FN-8239: align task card oversight icon state
Keep task-card overseer indicators aligned with resolved oversight settings. - Gate transient Eye badges on the resolved effective oversight level. - Suppress stale snapshots and empty header wrappers while inherited workflow settings load. - Add regression coverage and document the consistent off-state behavior. Files changed: .changeset/fn-8239-oversight-icon-consistency.md | 7 ++++ docs/dashboard-guide.md | 3 +- packages/dashboard/app/components/TaskCard.tsx | 29 ++++++++++++---- .../__tests__/TaskCard.oversight.test.tsx | 40 ++++++++++++++++++++++ .../app/components/__tests__/TaskCard.test.tsx | 23 +++++++++++++ 5 files changed, 94 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-8239 Fusion-Task-Lineage: 2857358c-7626-4b04-b1b5-fa51c3a71418 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8239-oversight-icon-consistency.md
Normal file
7
.changeset/fn-8239-oversight-icon-consistency.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: The board card overseer eye icon now hides when a task's oversight is off, matching the task detail.
|
||||
category: fix
|
||||
dev: TaskCard gates the planner-overseer Eye badge AND the card-header-badges wrapper predicate on the freshly-resolved effectiveOversightLevel (not just the transient snapshot's stale oversightLevel) via a shared showPlannerOverseerStateBadge boolean, so a stale non-off snapshot can no longer show an oversight icon or leave an empty header-badge shell while the Task Detail reads off.
|
||||
@@ -234,9 +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: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"`) when the engine reports a non-idle transient `plannerOverseerState`. 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 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.
|
||||
<!-- 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.
|
||||
|
||||
@@ -1697,6 +1697,23 @@ function TaskCardComponent({
|
||||
effectiveOversightLevel !== "off" &&
|
||||
!isInheritedDefaultOversightLevel;
|
||||
|
||||
/*
|
||||
* FNXC:PlannerOversight 2026-07-18-00:00:
|
||||
* FN-8239 requires the transient Eye badge and its header-wrapper gate to
|
||||
* share the freshly-resolved effective level used by the level badge and
|
||||
* Task Detail trigger. An inherited task with an unresolved workflow tier
|
||||
* must wait for that fetch; a stale non-off snapshot must not guess a level,
|
||||
* show an icon, or leave an empty header-badge shell in that window.
|
||||
*/
|
||||
const plannerOverseerState = task.plannerOverseerState;
|
||||
const showPlannerOverseerStateBadge = Boolean(
|
||||
plannerOverseerState
|
||||
&& plannerOverseerState.state !== "idle"
|
||||
&& plannerOverseerState.oversightLevel !== "off"
|
||||
&& (hasTaskOversightOverride || workflowOversightResolved)
|
||||
&& effectiveOversightLevel !== "off",
|
||||
);
|
||||
|
||||
/*
|
||||
* FNXC:PlannerOversight 2026-07-04-HH:MM:
|
||||
* FN-7542 removed the active-overseer-state ("Executor") chip that used to
|
||||
@@ -2886,7 +2903,7 @@ function TaskCardComponent({
|
||||
|| Boolean(hasTaskAgeStaleness && taskAgeStalenessCopy)
|
||||
|| Boolean(isStuck && (isPaused || !task.status || task.status === "queued"))
|
||||
|| Boolean(Array.isArray((task as TaskWithBranchProgress).branchProgress) && (task as TaskWithBranchProgress).branchProgress!.length > 0)
|
||||
|| Boolean(task.plannerOverseerState && task.plannerOverseerState.state !== "idle")
|
||||
|| showPlannerOverseerStateBadge
|
||||
|| Boolean(showStalledReview && stalledReview)
|
||||
|| Boolean(livePrInfo || liveIssueInfo)
|
||||
|| Boolean(task.gitlabTracking?.item)
|
||||
@@ -3150,15 +3167,13 @@ function TaskCardComponent({
|
||||
The engine clears this runtime at the source, but a client payload must never leak
|
||||
the Eye badge for an oversight-off in-progress or in-review task.
|
||||
*/}
|
||||
{task.plannerOverseerState
|
||||
&& task.plannerOverseerState.state !== "idle"
|
||||
&& task.plannerOverseerState.oversightLevel !== "off" && (
|
||||
{showPlannerOverseerStateBadge && plannerOverseerState && (
|
||||
<span
|
||||
className="card-status-badge card-planner-overseer-state"
|
||||
title={plannerOverseerBadgeTooltip(task.plannerOverseerState, t)}
|
||||
aria-label={plannerOverseerStateLabel(task.plannerOverseerState.state, t)}
|
||||
title={plannerOverseerBadgeTooltip(plannerOverseerState, t)}
|
||||
aria-label={plannerOverseerStateLabel(plannerOverseerState.state, t)}
|
||||
data-testid="planner-overseer-state-badge"
|
||||
data-planner-overseer-state={task.plannerOverseerState.state}
|
||||
data-planner-overseer-state={plannerOverseerState.state}
|
||||
>
|
||||
<Eye aria-hidden="true" />
|
||||
</span>
|
||||
|
||||
@@ -231,6 +231,46 @@ describe("TaskCard overseer-state badge removed (FN-7542)", () => {
|
||||
});
|
||||
|
||||
describe("TaskCard workflow-effective oversight level (FN-7516 code-review fix)", () => {
|
||||
it.each(["in-progress", "in-review"] as const)("hides the stale non-off overseer snapshot and header wrapper when workflow-effective oversight resolves off in %s", async (column) => {
|
||||
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||
stored: { plannerOversightLevel: "off" },
|
||||
effective: { plannerOversightLevel: "off" },
|
||||
orphaned: [],
|
||||
});
|
||||
|
||||
renderCard(
|
||||
{
|
||||
column,
|
||||
status: undefined,
|
||||
plannerOverseerState: {
|
||||
state: "watching",
|
||||
oversightLevel: "autonomous",
|
||||
watchedStage: column === "in-review" ? "reviewer" : "executor",
|
||||
signal: "progressing",
|
||||
attemptCount: 0,
|
||||
attemptLimit: 3,
|
||||
pendingConfirmation: false,
|
||||
observedAt: 1700000000000,
|
||||
},
|
||||
},
|
||||
{ workflowBadge: { workflowId: `wf-stale-snapshot-off-${column}`, workflowName: "Configured Off" } },
|
||||
);
|
||||
|
||||
// FNXC:PlannerOversight 2026-07-18-00:00: An inherited workflow's tier is
|
||||
// unresolved on first render. The stale runtime snapshot must not leak an
|
||||
// Eye badge or header wrapper before the configured-off response arrives.
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchWorkflowSettingValues).toHaveBeenCalledWith(`wf-stale-snapshot-off-${column}`, undefined);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves the workflow's effective plannerOversightLevel (not the schema default) when no per-task override is set", async () => {
|
||||
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||
stored: { plannerOversightLevel: "off" },
|
||||
|
||||
@@ -320,6 +320,28 @@ describe("TaskCard", () => {
|
||||
expect(screen.getByTestId("planner-overseer-state-badge")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(["in-progress", "in-review"] as const)("hides the stale non-off overseer snapshot and header wrapper when a task override resolves oversight off in %s", (column) => {
|
||||
const staleSnapshotTask = makeTask({
|
||||
column,
|
||||
plannerOversightLevel: "off",
|
||||
plannerOverseerState: {
|
||||
state: "watching",
|
||||
oversightLevel: "autonomous",
|
||||
watchedStage: column === "in-review" ? "reviewer" : "executor",
|
||||
signal: "progressing",
|
||||
attemptCount: 0,
|
||||
attemptLimit: 3,
|
||||
pendingConfirmation: false,
|
||||
observedAt: 1700000000000,
|
||||
},
|
||||
});
|
||||
|
||||
render(<TaskCard task={staleSnapshotTask} onOpenDetail={noop} addToast={noop} />);
|
||||
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("card-header-badges")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render an overseer badge for a stale non-idle oversight-off snapshot", () => {
|
||||
const staleOffTask = makeTask({
|
||||
column: "in-review",
|
||||
@@ -338,6 +360,7 @@ describe("TaskCard", () => {
|
||||
render(<TaskCard task={staleOffTask} onOpenDetail={noop} addToast={noop} />);
|
||||
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("card-header-badges")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// FN-7563: the badge used to print the raw kebab-case state (e.g.
|
||||
|
||||
Reference in New Issue
Block a user