FN-8251: hide overseer eye when oversight is off

Resolve inherited workflow oversight before rendering task-card overseer indicators.

- Resolve selected-workflow oversight using the planning workflow identity
- Fail closed for unknown, pending, malformed, and failed inherited settings
- Cover workflow changes and mobile suppression, and document the behavior
- Add a patch changeset for the corrected card indicator

Files changed:
 .changeset/fn-8251-card-eye-effective-oversight.md |   7 +
 docs/dashboard-guide.md                            |   4 +-
 packages/dashboard/app/components/TaskCard.tsx     | 116 ++++++++-------
 .../__tests__/TaskCard.oversight.test.tsx          | 161 ++++++++++++++++++++-
 .../app/components/__tests__/TaskCard.test.tsx     |   6 +-
 5 files changed, 234 insertions(+), 60 deletions(-)

Fusion-Task-Id: FN-8251

Fusion-Task-Lineage: 72dfa67e-e6d5-4557-91b3-23aff6dcf9e7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-17 16:38:29 -07:00
parent eee04bc1a8
commit b0654031bc
5 changed files with 234 additions and 60 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Hide the task-card overseer eye when the selected workflow has oversight turned off.
category: fix
dev: TaskCard resolves planner oversight from the per-workflow board identity and fails closed when inherited oversight cannot be resolved.

View File

@@ -234,10 +234,10 @@ Features:
- Task cards show a read-only **oversight-level badge** (`Observe`, `Steer`, or `Auto-recovery`) in the meta-badges cluster reflecting the effective planner-oversight level, but only when oversight is *meaningfully configured* — an explicit per-task override (including an explicit `autonomous` override), or a resolved workflow/effective tier of `observe`/`steer` (`data-testid="card-oversight-badge"`). A card that merely **inherits** the schema default `autonomous` tier (no per-task override, no non-default workflow tier) renders no badge and no empty `.card-meta-badges` shell. The badge is also absent when the effective level is explicitly "off", **and** while an inherited (no per-task-override) workflow tier is still being resolved (in flight or not yet fetched) — it never shows a guessed default during that window.
<!-- FNXC:PlannerOversight 2026-07-04-HH:MM: FN-7542 removed the FN-7516 active-overseer-state ("Executor") indicator described above as unwanted per-card noise — it fired on nearly every in-progress card. The oversight-level badge documented above is unaffected. -->
<!-- FNXC:PlannerOversight 2026-07-11-00:00: FN-7592 reintroduced a compact active-overseer state indicator as an Eye glyph instead of a wide text badge, using the engine-provided transient plannerOverseerState rather than locally guessing from task fields. -->
<!-- FNXC:PlannerOversight 2026-07-18-00:00: FN-8239 requires the card Eye badge and card-header-badges wrapper to use the same freshly-resolved effective oversight level as Task Detail, preventing stale non-off runtime snapshots from contradicting the detail's EyeOff state or leaving an empty wrapper. -->
<!-- FNXC:PlannerOversight 2026-07-17-15:50: FN-8251 requires selected-workflow cards to resolve inherited oversight from their trusted board workflow ID when aggregate workflowBadge metadata is absent. Identity-less, pending, failed, and malformed inherited resolution fails closed: the Eye and otherwise-empty card-header-badges wrapper appear only after active effective oversight is positively resolved. -->
<!-- FNXC:TaskRevert 2026-07-16-00:00: FN-8066 adds durable source-task revert provenance to the shared board/List TaskCard footer. -->
- Completed and archived task cards show a compact **Reverted** footer chip after a clean or already-reverted git outcome has persisted the source task's revert marker; conflicts, AI undo tasks, and revert PRs awaiting merge do not show it.
- Task cards show a compact **planner-overseer eye badge** (`data-testid="planner-overseer-state-badge"`) only when the engine reports a non-idle, non-off transient `plannerOverseerState` **and** the task's freshly-resolved effective oversight level is not `off`. When effective oversight is off, the eye badge and otherwise-empty `.card-header-badges` wrapper are both absent, matching the Task Detail `EyeOff` state. The eye badge is an active-overseer state marker, not a human-read/view indicator: `watching` means passive monitoring, `steering`/`recovering` mean active guidance or recovery is underway, and `awaiting-confirmation` means a human decision is required before the overseer can continue. Hover exposes the composed tooltip with the overseer's reason, watched stage/signal, and pending-confirmation note when present.
- Task cards show a compact **planner-overseer eye badge** (`data-testid="planner-overseer-state-badge"`) only when the engine reports a non-idle, non-off transient `plannerOverseerState` **and** the task's effective oversight is positively resolved as active. Aggregate cards resolve inherited oversight through their task workflow badge; selected-workflow board cards resolve it through their trusted board workflow ID. Identity-less cards and pending, failed, or malformed inherited workflow-setting loads fail closed. When effective oversight is off or cannot be positively resolved, the eye badge and otherwise-empty `.card-header-badges` wrapper are both absent. This matches—but does not alter—the separate Task Detail `EyeOff` menu trigger. The eye badge is an active-overseer state marker, not a human-read/view indicator: `watching` means passive monitoring, `steering`/`recovering` mean active guidance or recovery is underway, and `awaiting-confirmation` means a human decision is required before the overseer can continue. Hover exposes the composed tooltip with the overseer's reason, watched stage/signal, and pending-confirmation note when present.
<!-- FNXC:PlannerOversight 2026-07-04-17:00: FN-7517 adds interactive task-detail planner-overseer controls (quick level change, manual nudge, stop oversight, explain current action) alongside the FN-7516 read-only card badges above. These controls live ONLY in TaskDetailModal, not TaskCard.
FNXC:PlannerOversight 2026-07-05-00:00: FN-7604 collapses the desktop inline cluster (documented below through FN-7545/FN-7546) into the single universal overflow-menu dropdown that FN-7545 originally built for mobile only — the dropdown is now the ONE canonical surface on every viewport, desktop included.

View File

@@ -147,7 +147,14 @@ async function getAgentName(agentId: string, projectId?: string): Promise<string
* possible follow-up (see FN-7516 delivery notes) but is no longer required
* for correctness.
*/
const workflowOversightEffectiveCache = new Map<string, PlannerOversightLevel | undefined>();
type WorkflowOversightResolution = {
level: PlannerOversightLevel | undefined;
resolved: boolean;
/** FNXC:PlannerOversight 2026-07-17-15:50: Cache identity prevents an old workflow's active tier leaking during a prop switch. */
workflowCacheKey?: string;
};
const workflowOversightEffectiveCache = new Map<string, WorkflowOversightResolution>();
const workflowOversightInflight = new Map<string, Promise<void>>();
/** @internal Test helper to reset the workflow-effective-oversight cache between tests */
@@ -160,29 +167,35 @@ function getWorkflowOversightCacheKey(workflowId: string, projectId?: string): s
return `${projectId ?? "default"}::${workflowId}`;
}
function normalizeWorkflowId(value: string | null | undefined): string | undefined {
const normalized = value?.trim();
return normalized || undefined;
}
function isPlannerOversightLevelValue(value: unknown): value is PlannerOversightLevel {
return typeof value === "string" && (PLANNER_OVERSIGHT_LEVELS as readonly string[]).includes(value);
}
/** Fetch (with in-flight de-dup) and cache the workflow's effective
* `plannerOversightLevel` setting value for a given `(workflowId, projectId)`.
* Never throws — an error caches `undefined` so the resolver falls through to
* the schema default rather than retrying every render. */
async function loadWorkflowOversightEffectiveLevel(workflowId: string, projectId: string | undefined): Promise<PlannerOversightLevel | undefined> {
* Never throws; failed or malformed responses stay explicitly unresolved so
* callers cannot mistake an unknown inherited tier for the schema default. */
async function loadWorkflowOversightEffectiveLevel(workflowId: string, projectId: string | undefined): Promise<WorkflowOversightResolution> {
const key = getWorkflowOversightCacheKey(workflowId, projectId);
if (workflowOversightEffectiveCache.has(key)) {
return workflowOversightEffectiveCache.get(key);
}
const cached = workflowOversightEffectiveCache.get(key);
if (cached) return cached;
let inflight = workflowOversightInflight.get(key);
if (!inflight) {
inflight = fetchWorkflowSettingValues(workflowId, projectId)
.then((payload) => {
const raw = payload.effective?.plannerOversightLevel;
workflowOversightEffectiveCache.set(key, isPlannerOversightLevelValue(raw) ? raw : undefined);
workflowOversightEffectiveCache.set(key, isPlannerOversightLevelValue(raw)
? { level: raw, resolved: true }
: { level: undefined, resolved: false });
})
.catch(() => {
workflowOversightEffectiveCache.set(key, undefined);
workflowOversightEffectiveCache.set(key, { level: undefined, resolved: false });
})
.finally(() => {
workflowOversightInflight.delete(key);
@@ -190,7 +203,7 @@ async function loadWorkflowOversightEffectiveLevel(workflowId: string, projectId
workflowOversightInflight.set(key, inflight);
}
await inflight;
return workflowOversightEffectiveCache.get(key);
return workflowOversightEffectiveCache.get(key) ?? { level: undefined, resolved: false };
}
function normalizeTaskPriorityValue(priority: Task["priority"]): TaskPriority {
@@ -1022,65 +1035,56 @@ function TaskCardComponent({
}, [agentsMap, task.assignedAgentId, projectId]);
/*
* FNXC:PlannerOversight 2026-07-04-16:00:
* Fetch (and cache, see loadWorkflowOversightEffectiveLevel above) the
* workflow's effective plannerOversightLevel setting so the card can
* resolve the TRUE effective oversight tier for tasks with no per-task
* override, rather than always falling through to the schema default
* (FN-7516 code-review fix). `Task` has no `workflowId` field (workflow
* selection lives in the separate `task_workflow_selection` table, not on
* the task payload — verified against packages/core/src/types.ts), so this
* reads the workflow id from the already-existing `workflowBadge` prop
* (populated by Column/WorktreeGroup board callers). Only fires when a
* workflowBadge.workflowId is present; synchronous cache hits skip the
* state churn entirely. Surfaces that don't pass `workflowBadge` (dock,
* MainContent) fall back to the resolver's schema default immediately
* (treated as "resolved" — there is no pending fetch to gate on for those
* surfaces).
*
* Second code-review fix (this pass): while the workflow-tier fetch is
* in flight, `workflowOversightEffectiveLevel` was `undefined`, which the
* resolver treats identically to "no workflow setting exists" and falls
* back to `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous"). That rendered
* a wrong default badge for tasks inheriting a workflow explicitly
* configured to Off/Observe/Steer, for the whole window before the fetch
* resolved (and forever on fetch failure, since failures also cache
* `undefined`). Track resolution explicitly via `workflowOversightResolved`
* and gate the badge (`showOversightBadge` below) so nothing renders from
* the unresolved workflow tier — only a task-level override (known
* synchronously from the task payload) can show a badge before the
* workflow tier is known.
* FNXC:PlannerOversight 2026-07-17-15:50:
* FN-8251 requires per-workflow cards to resolve inherited oversight with
* their trusted selected `planningWorkflowId` when aggregate-only
* `workflowBadge` metadata is absent. Prefer a task-specific aggregate ID;
* normalize blank IDs; and fail closed for identity-less, pending, failed,
* or malformed inherited resolution. The Eye is allowed only when effective
* oversight is positively known active, while a valid task override remains
* authoritative without a workflow fetch.
*/
const workflowIdForOversight = workflowBadge?.workflowId;
const [workflowOversightState, setWorkflowOversightState] = useState<{ level: PlannerOversightLevel | undefined; resolved: boolean }>(() => {
if (!workflowIdForOversight) return { level: undefined, resolved: true };
const key = getWorkflowOversightCacheKey(workflowIdForOversight, projectId);
return workflowOversightEffectiveCache.has(key)
? { level: workflowOversightEffectiveCache.get(key), resolved: true }
: { level: undefined, resolved: false };
const workflowIdForOversight = normalizeWorkflowId(workflowBadge?.workflowId)
?? normalizeWorkflowId(planningWorkflowId);
const workflowOversightCacheKey = workflowIdForOversight
? getWorkflowOversightCacheKey(workflowIdForOversight, projectId)
: undefined;
const [workflowOversightState, setWorkflowOversightState] = useState<WorkflowOversightResolution>(() => {
if (!workflowOversightCacheKey) return { level: undefined, resolved: false };
return {
...(workflowOversightEffectiveCache.get(workflowOversightCacheKey) ?? { level: undefined, resolved: false }),
workflowCacheKey: workflowOversightCacheKey,
};
});
useEffect(() => {
if (!workflowIdForOversight) {
setWorkflowOversightState({ level: undefined, resolved: true });
if (!workflowIdForOversight || !workflowOversightCacheKey) {
setWorkflowOversightState({ level: undefined, resolved: false });
return;
}
const workflowId = workflowIdForOversight;
const key = getWorkflowOversightCacheKey(workflowId, projectId);
if (workflowOversightEffectiveCache.has(key)) {
setWorkflowOversightState({ level: workflowOversightEffectiveCache.get(key), resolved: true });
const key = workflowOversightCacheKey;
const cached = workflowOversightEffectiveCache.get(key);
if (cached) {
setWorkflowOversightState({ ...cached, workflowCacheKey: key });
return;
}
setWorkflowOversightState({ level: undefined, resolved: false });
setWorkflowOversightState({ level: undefined, resolved: false, workflowCacheKey: key });
let cancelled = false;
void loadWorkflowOversightEffectiveLevel(workflowId, projectId).then((level) => {
if (!cancelled) setWorkflowOversightState({ level, resolved: true });
void loadWorkflowOversightEffectiveLevel(workflowId, projectId).then((resolution) => {
if (!cancelled) setWorkflowOversightState({ ...resolution, workflowCacheKey: key });
});
return () => { cancelled = true; };
}, [workflowIdForOversight, projectId]);
const workflowOversightEffectiveLevel = workflowOversightState.level;
const workflowOversightResolved = workflowOversightState.resolved;
}, [workflowIdForOversight, workflowOversightCacheKey, projectId]);
// FNXC:PlannerOversight 2026-07-17-15:50: Switching a memoized card between
// workflows must fail closed in the render before its effect resets state;
// an active tier resolved for the prior workflow cannot authorize this Eye.
const currentWorkflowOversightState = workflowOversightState.workflowCacheKey === workflowOversightCacheKey
? workflowOversightState
: { level: undefined, resolved: false };
const workflowOversightEffectiveLevel = currentWorkflowOversightState.level;
const workflowOversightResolved = currentWorkflowOversightState.resolved;
// Auto-focus and auto-resize description textarea when entering edit mode
useEffect(() => {

View File

@@ -98,7 +98,14 @@ function makeTask(overrides: Partial<Task> = {}): Task {
const noop = () => {};
function renderCard(overrides: Partial<Task> = {}, cardProps: { workflowBadge?: { workflowId: string; workflowName: string; workflowIcon?: string } } = {}) {
function renderCard(
overrides: Partial<Task> = {},
cardProps: {
workflowBadge?: { workflowId: string; workflowName: string; workflowIcon?: string };
planningWorkflowId?: string | null;
projectId?: string;
} = {},
) {
return render(<TaskCard task={makeTask(overrides)} onOpenDetail={noop} addToast={noop} {...cardProps} />);
}
@@ -380,6 +387,147 @@ describe("TaskCard workflow-effective oversight level (FN-7516 code-review fix)"
});
});
/*
* FNXC:PlannerOversight 2026-07-17-15:50:
* FN-8251 regression coverage keeps card-level inherited workflow resolution
* fail-closed. Selected-workflow boards provide `planningWorkflowId` without
* aggregate `workflowBadge` metadata, so no stale runtime Eye may appear
* unless the selected workflow's effective oversight is positively active.
*/
describe("TaskCard selected-workflow oversight identity (FN-8251)", () => {
const staleSnapshot = (column: "in-progress" | "in-review") => ({
column,
status: undefined,
plannerOverseerState: {
state: "watching" as const,
oversightLevel: "autonomous" as const,
watchedStage: column === "in-review" ? "reviewer" as const : "executor" as const,
signal: "progressing" as const,
attemptCount: 0,
attemptLimit: 3,
pendingConfirmation: false,
observedAt: 1700000000000,
},
});
it.each(["in-progress", "in-review"] as const)("uses planningWorkflowId and hides the stale eye before and after selected-workflow off resolves in %s", async (column) => {
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
stored: { plannerOversightLevel: "off" },
effective: { plannerOversightLevel: "off" },
orphaned: [],
});
renderCard(staleSnapshot(column), { planningWorkflowId: " selected-workflow-off ", projectId: "project-8251" });
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
expect(screen.queryByTestId("card-header-badges")).toBeNull();
await waitFor(() => {
expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("selected-workflow-off", "project-8251");
});
await waitFor(() => {
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
expect(screen.queryByTestId("card-header-badges")).toBeNull();
});
});
it.each([
["rejected", () => vi.mocked(fetchWorkflowSettingValues).mockRejectedValueOnce(new Error("unavailable"))],
["missing", () => vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({ stored: {}, effective: {}, orphaned: [] })],
["invalid", () => vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({ stored: {}, effective: { plannerOversightLevel: "unknown" }, orphaned: [] })],
])("fails closed after a %s inherited workflow resolution", async (_state, arrange) => {
arrange();
renderCard(staleSnapshot("in-progress"), { planningWorkflowId: `workflow-${_state}` });
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalled());
await waitFor(() => {
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
expect(screen.queryByTestId("card-header-badges")).toBeNull();
expect(screen.queryByLabelText(/overseer/i)).toBeNull();
});
});
it("fails closed without workflow identity but keeps a valid active task override authoritative", () => {
const { rerender } = renderCard(staleSnapshot("in-progress"));
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
expect(screen.queryByTestId("card-header-badges")).toBeNull();
rerender(
<TaskCard
task={makeTask({ ...staleSnapshot("in-progress"), plannerOversightLevel: "steer" })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(screen.getByTestId("planner-overseer-state-badge")).toBeTruthy();
});
it.each([
["aggregate", { workflowBadge: { workflowId: "aggregate-active", workflowName: "Aggregate active" } }],
["selected workflow", { planningWorkflowId: "selected-active" }],
])("renders the eye only after positively resolved active oversight for %s cards", async (_surface, props) => {
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
stored: { plannerOversightLevel: "steer" },
effective: { plannerOversightLevel: "steer" },
orphaned: [],
});
renderCard(staleSnapshot("in-progress"), props);
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
expect(await screen.findByTestId("planner-overseer-state-badge")).toBeTruthy();
});
it("re-resolves when the selected workflow identity changes", async () => {
vi.mocked(fetchWorkflowSettingValues)
.mockResolvedValueOnce({ stored: { plannerOversightLevel: "off" }, effective: { plannerOversightLevel: "off" }, orphaned: [] })
.mockResolvedValueOnce({ stored: { plannerOversightLevel: "observe" }, effective: { plannerOversightLevel: "observe" }, orphaned: [] });
const task = makeTask(staleSnapshot("in-progress"));
const { rerender } = render(
<TaskCard task={task} onOpenDetail={noop} addToast={noop} planningWorkflowId="first-workflow" />,
);
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("first-workflow", undefined));
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
rerender(<TaskCard task={task} onOpenDetail={noop} addToast={noop} planningWorkflowId="second-workflow" />);
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("second-workflow", undefined));
expect(await screen.findByTestId("planner-overseer-state-badge")).toBeTruthy();
});
it("hides the eye synchronously when an active selected workflow changes to an unresolved off workflow", async () => {
vi.mocked(fetchWorkflowSettingValues)
.mockResolvedValueOnce({ stored: { plannerOversightLevel: "steer" }, effective: { plannerOversightLevel: "steer" }, orphaned: [] })
.mockResolvedValueOnce({ stored: { plannerOversightLevel: "off" }, effective: { plannerOversightLevel: "off" }, orphaned: [] });
const task = makeTask(staleSnapshot("in-progress"));
const { rerender } = render(
<TaskCard task={task} onOpenDetail={noop} addToast={noop} planningWorkflowId="active-workflow" />,
);
expect(await screen.findByTestId("planner-overseer-state-badge")).toBeTruthy();
rerender(<TaskCard task={task} onOpenDetail={noop} addToast={noop} planningWorkflowId="off-workflow" />);
// FNXC:PlannerOversight 2026-07-17-15:50: A useEffect reset is too late:
// this render must not reuse the prior workflow's active resolution while
// the selected off workflow is loading.
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("off-workflow", undefined));
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
});
it("keeps selected-workflow suppression at the 375px mobile viewport", async () => {
Object.defineProperty(window, "innerWidth", { value: 375, configurable: true });
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
stored: { plannerOversightLevel: "off" },
effective: { plannerOversightLevel: "off" },
orphaned: [],
});
renderCard(staleSnapshot("in-review"), { planningWorkflowId: "mobile-off" });
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("mobile-off", undefined));
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
expect(screen.queryByTestId("card-header-badges")).toBeNull();
});
});
/*
* FNXC:PlannerOversight 2026-07-04-HH:MM:
* FN-7542 dropped the `pausedReason`/`reviewState`/`workflowTransitionNotification`
@@ -412,6 +560,17 @@ describe("TaskCard memo comparator — oversight level (FN-7516)", () => {
).toBe(false);
});
it("returns false when planningWorkflowId changes, so selected-workflow cards re-resolve their effective tier", () => {
const task = makeTask({});
expect(
__test_areTaskCardPropsEqual(
{ task, planningWorkflowId: "wf-a", onOpenDetail: noop, addToast: noop } as any,
{ task, planningWorkflowId: "wf-b", onOpenDetail: noop, addToast: noop } as any,
),
).toBe(false);
});
it("returns true when nothing relevant changes, including plannerOversightLevel", () => {
const base = makeTask({ plannerOversightLevel: "steer" });
const same = makeTask({ plannerOversightLevel: "steer" });

View File

@@ -292,8 +292,9 @@ describe("TaskCard", () => {
});
it("repaints the memoized card when plannerOverseerState changes, and renders nothing when absent", () => {
const idleTask = makeTask({ plannerOverseerState: undefined });
const idleTask = makeTask({ plannerOversightLevel: "autonomous", plannerOverseerState: undefined });
const watchingTask = makeTask({
plannerOversightLevel: "autonomous",
plannerOverseerState: {
state: "watching",
oversightLevel: "autonomous",
@@ -370,6 +371,7 @@ describe("TaskCard", () => {
it("explains an in-review awaiting-confirmation badge with a readable label and a reason-bearing tooltip", () => {
const task = makeTask({
column: "in-review",
plannerOversightLevel: "autonomous",
plannerOverseerState: {
state: "awaiting-confirmation",
oversightLevel: "autonomous",
@@ -403,6 +405,7 @@ describe("TaskCard", () => {
it("renders readable labels for in-progress watching and recovering overseer states", () => {
const watchingTask = makeTask({
column: "in-progress",
plannerOversightLevel: "autonomous",
plannerOverseerState: {
state: "watching",
oversightLevel: "autonomous",
@@ -426,6 +429,7 @@ describe("TaskCard", () => {
const recoveringTask = makeTask({
column: "in-progress",
plannerOversightLevel: "autonomous",
plannerOverseerState: {
state: "recovering",
oversightLevel: "autonomous",