diff --git a/.changeset/fn-7539-oversight-badge-default.md b/.changeset/fn-7539-oversight-badge-default.md new file mode 100644 index 0000000000..c9ab7eaf20 --- /dev/null +++ b/.changeset/fn-7539-oversight-badge-default.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Task cards no longer show the "Auto-recovery" oversight badge unless oversight is explicitly configured. +category: fix +dev: `TaskCard.tsx`'s `showOversightBadge` gate now also suppresses the badge when the effective level equals `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous") and there is no explicit per-task `plannerOversightLevel` override; an explicit per-task override of "autonomous" still renders the badge. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 6ec4d7c892..4897a8bf2d 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -183,7 +183,8 @@ Features: - Task card header meta badges group priority, fast mode, agent-created provenance, workflow identity, and elapsed/created-time chips into one wrapping row; agent labels prefer `sourceMetadata.agentName` over raw agent IDs -- 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 (a task's explicit override wins and renders immediately; otherwise the card resolves the task's workflow-configured effective `plannerOversightLevel` when the board supplies a `workflowBadge`, falling back to the schema default `Auto-recovery` only when neither tier resolves) (`data-testid="card-oversight-badge"`); the badge is absent (no empty shell) 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. + +- 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. - Task cards also show a read-only **active-overseer-state indicator** (`.card-overseer-state-badge`, `data-testid="card-overseer-state-badge"`) — "Executor", "Reviewer", "Merger", "Pull request", or "Workflow gate" — while the task is in a monitorable stage (in-progress/in-review, or paused on a workflow input/approval gate) and the effective oversight level is not "off" (and is known, per the same resolution gate as the oversight badge above). The indicator is suppressed (no empty shell) when the task is user-paused, agent-paused off a workflow gate, `done`, or `archived`. - The task detail modal's inline meta-controls cluster (next to Priority/Execution mode) adds four planner-overseer controls: a **quick oversight-level select** (`data-testid="detail-oversight-level-select"`) that writes the per-task `plannerOversightLevel` override (Off/Observe/Steer/Autonomous recovery) or clears it back to the inherited workflow/project default via an "Inherit" option; a **manual nudge** button (`data-testid="detail-overseer-nudge"`) that asks the overseer to inject one guidance-only steering comment into the currently watched stage right now (never a merge/PR/destructive action), disabled when the overseer is off/inactive or the task is user-paused/done/archived/`autoMerge:false` in-review; a **stop oversight** button (`data-testid="detail-overseer-stop"`) that disables active oversight for the task (confirmation-gated), hidden once oversight is already off; and an **explain current action** button (`data-testid="detail-overseer-explain"`) that toggles a small read-only panel (`data-testid="detail-overseer-explain-panel"`) showing the overseer's watched stage, reason, last action, and attempt count/limit, with a non-empty-shell inactive state when the overseer is not currently watching. All three action controls call the `POST /tasks/:id/overseer/nudge`, `POST /tasks/:id/overseer/stop`, and `GET /tasks/:id/overseer/explain` routes. diff --git a/packages/dashboard/app/components/TaskCard.tsx b/packages/dashboard/app/components/TaskCard.tsx index 8a221dc265..70e28bc7c0 100644 --- a/packages/dashboard/app/components/TaskCard.tsx +++ b/packages/dashboard/app/components/TaskCard.tsx @@ -6,6 +6,7 @@ import { createPortal } from "react-dom"; import { Link, Clock, Layers, Pencil, ChevronDown, Folder, Target, Bot, Trash2, RotateCw, Zap, GitBranch, GitPullRequest, AlertTriangle, ArrowUpRight, Eye } from "lucide-react"; import type { Task, TaskDetail, Column, ColumnId, PrInfo, IssueInfo, TaskPriority, GithubIssueAction, MergeResult, PlannerOversightLevel } from "@fusion/core"; import { + DEFAULT_PLANNER_OVERSIGHT_LEVEL, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, PLANNER_OVERSIGHT_LEVELS, @@ -1702,14 +1703,32 @@ function TaskCardComponent({ * default/guessed level. Only an effective level that resolves to "off" * renders no badge either (no empty shell) — see the * `hasCardMetaBadges`/render guard below. + * + * FN-7539: the round-2 fix above still showed the badge on virtually every + * card, because a task with no per-task override and no explicit + * non-default workflow tier resolves to the schema default + * (`DEFAULT_PLANNER_OVERSIGHT_LEVEL`, "autonomous") — and that default was + * still treated as "resolved" and rendered. An inherited default is not + * meaningfully-configured oversight, so it must not surface a per-card + * badge. Narrowed: suppress the badge when the effective level equals the + * schema default AND there is no explicit per-task override — i.e. the + * default was reached purely by inheritance (no override, no non-default + * workflow tier). An EXPLICIT per-task override of "autonomous" still + * renders the badge (explicit intent is preserved, not treated as + * inherited default), and a workflow tier that explicitly resolves to + * "autonomous" also renders nothing, matching the inherited-default case. */ const hasTaskOversightOverride = isPlannerOversightLevelValue(task.plannerOversightLevel); const effectiveOversightLevel: PlannerOversightLevel = resolveEffectivePlannerOversightLevel( task.plannerOversightLevel, workflowOversightEffectiveLevel, ); + const isInheritedDefaultOversightLevel = + !hasTaskOversightOverride && effectiveOversightLevel === DEFAULT_PLANNER_OVERSIGHT_LEVEL; const showOversightBadge = - (hasTaskOversightOverride || workflowOversightResolved) && effectiveOversightLevel !== "off"; + (hasTaskOversightOverride || workflowOversightResolved) && + effectiveOversightLevel !== "off" && + !isInheritedDefaultOversightLevel; /* * FNXC:PlannerOversight 2026-07-04-00:00: diff --git a/packages/dashboard/app/components/__tests__/TaskCard.oversight.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.oversight.test.tsx index 638f459507..26848dfd09 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.oversight.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.oversight.test.tsx @@ -4,8 +4,10 @@ * and the active-overseer-state indicator. Covers the Surface Enumeration data * states: Observe/Steer/Autonomous render a labeled badge; an explicit "off" * effective level renders nothing (no empty shell); an unset per-task override - * resolves to the schema default ("autonomous") rather than rendering nothing, - * per code review. The overseer-state indicator renders only while the + * that resolves to the schema default ("autonomous") renders NO badge at all + * (FN-7539: an inherited default is not meaningfully-configured oversight), + * while an EXPLICIT per-task override of "autonomous" still renders the + * badge (explicit intent is preserved). The overseer-state indicator renders only while the * card-local watched-stage derivation (`deriveOverseerCardWatchedStage` in * TaskCard.tsx, mirroring the engine's `resolveWatchedStage`) resolves a stage * AND the task is not paused/done/archived AND the effective oversight level @@ -111,9 +113,15 @@ describe("TaskCard effective oversight-level badge (FN-7516)", () => { expect(screen.queryByTestId("card-oversight-badge")).toBeNull(); }); - it("resolves the schema default (autonomous) — and renders the badge — when the level field is undefined", () => { + it("renders no badge (no empty shell) when the level field is undefined and it resolves to the inherited schema default (autonomous) (FN-7539)", () => { renderCard({ column: "todo" }); + expect(screen.queryByTestId("card-oversight-badge")).toBeNull(); + }); + + it("renders the badge when the level is EXPLICITLY overridden to autonomous (explicit intent preserved) (FN-7539)", () => { + renderCard({ plannerOversightLevel: "autonomous", column: "todo" }); + const badge = screen.getByTestId("card-oversight-badge"); expect(badge).toBeTruthy(); expect(badge.className).toContain("card-oversight-badge--autonomous"); @@ -130,6 +138,15 @@ describe("TaskCard effective oversight-level badge (FN-7516)", () => { expect(metaBadges.querySelector(".card-oversight-badge")).toBeNull(); } }); + + it("does not render an always-on empty card-meta-badges child for the inherited-default case (FN-7539)", () => { + const { container } = renderCard({ column: "todo" }); + + const metaBadges = container.querySelector(".card-meta-badges"); + if (metaBadges) { + expect(metaBadges.querySelector(".card-oversight-badge")).toBeNull(); + } + }); }); describe("TaskCard active-overseer-state indicator (FN-7516)", () => { @@ -259,10 +276,12 @@ describe("TaskCard workflow-effective oversight level (FN-7516 code-review fix)" expect(screen.queryByTestId("card-overseer-state-badge")).toBeNull(); // Once the fetch resolves (workflow has no oversight setting → schema - // default applies), the badge appears. + // default applies), the badge STILL does not render (FN-7539: an + // inherited default is not meaningfully-configured oversight). resolveFetch({ stored: {}, effective: {}, orphaned: [] }); - const badge = await screen.findByTestId("card-oversight-badge"); - expect(badge.className).toContain("card-oversight-badge--autonomous"); + await waitFor(() => { + expect(screen.queryByTestId("card-oversight-badge")).toBeNull(); + }); }); it("renders a per-task override immediately even while the workflow-tier fetch is pending", async () => { @@ -304,6 +323,23 @@ describe("TaskCard workflow-effective oversight level (FN-7516 code-review fix)" const badge = await screen.findByTestId("card-oversight-badge"); expect(badge.className).toContain("card-oversight-badge--observe"); }); + + it("renders no badge when the workflow's effective level explicitly resolves to autonomous (equals the inherited default) (FN-7539)", async () => { + vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({ + stored: { plannerOversightLevel: "autonomous" }, + effective: { plannerOversightLevel: "autonomous" }, + orphaned: [], + }); + + renderCard({ column: "todo" }, { workflowBadge: { workflowId: "wf-configured-autonomous", workflowName: "Configured Autonomous" } }); + + await waitFor(() => { + expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("wf-configured-autonomous", undefined); + }); + await waitFor(() => { + expect(screen.queryByTestId("card-oversight-badge")).toBeNull(); + }); + }); }); describe("TaskCard memo comparator — oversight level and overseer state (FN-7516)", () => { diff --git a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx index 384f156f7e..99eb03f826 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx @@ -2360,16 +2360,15 @@ describe("TaskCard", () => { expect(timer?.closest(".card-meta-badges")).toBeNull(); expect(timer?.closest(".card-footer-row-right")).not.toBeNull(); - // FNXC:PlannerOversight 2026-07-04-00:00: an unset per-task oversight override - // resolves to the schema default ("autonomous") via the FN-7516 card badge, - // so it now appears alongside the other opt-in meta badges (FN-7516). + // FNXC:PlannerOversight 2026-07-04-00:00: an unset per-task oversight + // override resolving to the inherited schema default ("autonomous") no + // longer renders a per-card badge (FN-7539) — an inherited default is not + // meaningfully-configured oversight, so it does not appear among the + // opt-in meta badges here. expect(Array.from(group?.children ?? []).map((child) => child.className)).toEqual([ "card-priority-badge card-priority-badge--high", "card-execution-mode-badge card-execution-mode-badge--fast", "card-agent-created-badge", - "card-oversight-badge card-oversight-badge--autonomous", - - ]); });