FN-8255: hide inherited-default oversight eye
Suppress transient task-card oversight eyes for unconfigured inherited autonomous workflows. - Reuse the meaningful oversight badge gate for planner-overseer eye rendering. - Cover aggregate, selected-workflow, explicit override, and mobile states. - Document the inherited-default suppression and add a patch changeset. Files changed: .changeset/fn-8255-card-eye-inherited-default-oversight.md | 7 ++ docs/dashboard-guide.md | 3 +- packages/dashboard/app/components/TaskCard.tsx | 21 +++--- .../__tests__/TaskCard.oversight.test.tsx | 78 +++++++++++++++++++--- 4 files changed, 88 insertions(+), 21 deletions(-) Fusion-Task-Id: FN-8255 Fusion-Task-Lineage: 2c48f96f-9f06-4def-b7bb-1020e8505d7b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Hide the task-card overseer eye when a workflow only uses the default (unconfigured) oversight level.
|
||||
category: fix
|
||||
dev: TaskCard's showPlannerOverseerStateBadge now reuses showOversightBadge, so the transient eye follows the same FN-7539 inherited-default suppression as the oversight-level badge — an autonomous tier reached purely by workflow inheritance (no explicit per-task/workflow override) renders no eye even with a stale non-idle plannerOverseerState. FN-8221/FN-8239/FN-8251 guards are unchanged.
|
||||
@@ -234,10 +234,11 @@ 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-01:35: FN-8255 requires the transient card Eye to use the same meaningfully-configured gate as the level badge. A workflow declaration-default autonomous tier reached purely by inheritance (no explicit task override) is suppressed even with a stale non-idle runtime snapshot; the Eye and otherwise-empty card-header-badges shell remain absent. -->
|
||||
<!-- 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 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.
|
||||
- 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 and meaningfully configured. The eye uses the same inherited-default suppression as the oversight-level badge: a workflow declaration-default `autonomous` tier with no explicit per-task override shows neither eye nor an otherwise-empty `.card-header-badges` wrapper, while an explicit per-task `autonomous` override or resolved `observe`/`steer` tier can show it. 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.
|
||||
|
||||
@@ -1702,20 +1702,21 @@ function TaskCardComponent({
|
||||
!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.
|
||||
* FNXC:PlannerOversight 2026-07-18-01:30:
|
||||
* FN-8255 requires the transient Eye badge and its header-wrapper gate to
|
||||
* reuse the same meaningfully-configured gate as the level badge. A workflow
|
||||
* declaration-default autonomous tier with no explicit task override is not
|
||||
* active card oversight, so a stale non-idle snapshot must not show an icon
|
||||
* or leave an empty header-badge shell; `showOversightBadge` preserves the
|
||||
* FN-7539 inherited-default suppression and the FN-8239/FN-8251 fail-closed
|
||||
* resolution guards.
|
||||
*/
|
||||
const plannerOverseerState = task.plannerOverseerState;
|
||||
const showPlannerOverseerStateBadge = Boolean(
|
||||
plannerOverseerState
|
||||
showOversightBadge
|
||||
&& plannerOverseerState
|
||||
&& plannerOverseerState.state !== "idle"
|
||||
&& plannerOverseerState.oversightLevel !== "off"
|
||||
&& (hasTaskOversightOverride || workflowOversightResolved)
|
||||
&& effectiveOversightLevel !== "off",
|
||||
&& plannerOverseerState.oversightLevel !== "off",
|
||||
);
|
||||
|
||||
/*
|
||||
|
||||
@@ -430,6 +430,55 @@ describe("TaskCard selected-workflow oversight identity (FN-8251)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["in-progress", "in-review"] as const)("suppresses inherited-default autonomous stale eyes before and after selected-workflow resolution in %s (FN-8255)", async (column) => {
|
||||
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||
stored: {},
|
||||
effective: { plannerOversightLevel: "autonomous" },
|
||||
orphaned: [],
|
||||
});
|
||||
|
||||
renderCard(staleSnapshot(column), { planningWorkflowId: `selected-inherited-default-${column}`, projectId: "project-8255" });
|
||||
|
||||
// The selected workflow's declaration default is not a meaningful oversight
|
||||
// configuration, so a stale runtime snapshot cannot create an eye or shell.
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(fetchWorkflowSettingValues).toHaveBeenCalledWith(`selected-inherited-default-${column}`, "project-8255");
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:PlannerOversight 2026-07-18-01:32:
|
||||
* FN-8255 requires every TaskCard provider to suppress a stale Eye for an
|
||||
* autonomous workflow declaration default without a task override. The
|
||||
* aggregate workflow-badge path must match selected-workflow board behavior
|
||||
* and leave no eye element with a title or aria-label behind.
|
||||
*/
|
||||
it("suppresses the inherited-default autonomous stale eye for the aggregate workflow-badge provider (FN-8255)", async () => {
|
||||
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||
stored: {},
|
||||
effective: { plannerOversightLevel: "autonomous" },
|
||||
orphaned: [],
|
||||
});
|
||||
const { container } = renderCard(staleSnapshot("in-progress"), {
|
||||
workflowBadge: { workflowId: "aggregate-inherited-default", workflowName: "All workflows" },
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("aggregate-inherited-default", undefined);
|
||||
});
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
expect(container.querySelector(".card-planner-overseer-state[title][aria-label]")).toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["rejected", () => vi.mocked(fetchWorkflowSettingValues).mockRejectedValueOnce(new Error("unavailable"))],
|
||||
["missing", () => vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({ stored: {}, effective: {}, orphaned: [] })],
|
||||
@@ -461,13 +510,20 @@ describe("TaskCard selected-workflow oversight identity (FN-8251)", () => {
|
||||
expect(screen.getByTestId("planner-overseer-state-badge")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps an explicit per-task autonomous override authoritative for the stale eye", () => {
|
||||
renderCard({ ...staleSnapshot("in-progress"), plannerOversightLevel: "autonomous" });
|
||||
|
||||
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) => {
|
||||
["aggregate observe", "observe", { workflowBadge: { workflowId: "aggregate-observe", workflowName: "Aggregate observe" } }],
|
||||
["aggregate steer", "steer", { workflowBadge: { workflowId: "aggregate-steer", workflowName: "Aggregate steer" } }],
|
||||
["selected workflow steer", "steer", { planningWorkflowId: "selected-steer" }],
|
||||
] as const)("renders the eye only after positively resolved active %s oversight", async (_surface, level, props) => {
|
||||
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||
stored: { plannerOversightLevel: "steer" },
|
||||
effective: { plannerOversightLevel: "steer" },
|
||||
stored: { plannerOversightLevel: level },
|
||||
effective: { plannerOversightLevel: level },
|
||||
orphaned: [],
|
||||
});
|
||||
renderCard(staleSnapshot("in-progress"), props);
|
||||
@@ -513,18 +569,20 @@ describe("TaskCard selected-workflow oversight identity (FN-8251)", () => {
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps selected-workflow suppression at the 375px mobile viewport", async () => {
|
||||
it("keeps selected-workflow inherited-default 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" },
|
||||
stored: {},
|
||||
effective: { plannerOversightLevel: "autonomous" },
|
||||
orphaned: [],
|
||||
});
|
||||
renderCard(staleSnapshot("in-review"), { planningWorkflowId: "mobile-off" });
|
||||
const { container } = renderCard(staleSnapshot("in-review"), { planningWorkflowId: "mobile-inherited-default" });
|
||||
|
||||
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("mobile-off", undefined));
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
await waitFor(() => expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("mobile-inherited-default", undefined));
|
||||
expect(screen.queryByTestId("planner-overseer-state-badge")).toBeNull();
|
||||
expect(screen.queryByTestId("card-header-badges")).toBeNull();
|
||||
expect(container.querySelector(".card-planner-overseer-state[title][aria-label]")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user