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.
|
- 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-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-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: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. -->
|
<!-- 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.
|
- 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-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.
|
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;
|
!isInheritedDefaultOversightLevel;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FNXC:PlannerOversight 2026-07-18-00:00:
|
* FNXC:PlannerOversight 2026-07-18-01:30:
|
||||||
* FN-8239 requires the transient Eye badge and its header-wrapper gate to
|
* FN-8255 requires the transient Eye badge and its header-wrapper gate to
|
||||||
* share the freshly-resolved effective level used by the level badge and
|
* reuse the same meaningfully-configured gate as the level badge. A workflow
|
||||||
* Task Detail trigger. An inherited task with an unresolved workflow tier
|
* declaration-default autonomous tier with no explicit task override is not
|
||||||
* must wait for that fetch; a stale non-off snapshot must not guess a level,
|
* active card oversight, so a stale non-idle snapshot must not show an icon
|
||||||
* show an icon, or leave an empty header-badge shell in that window.
|
* 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 plannerOverseerState = task.plannerOverseerState;
|
||||||
const showPlannerOverseerStateBadge = Boolean(
|
const showPlannerOverseerStateBadge = Boolean(
|
||||||
plannerOverseerState
|
showOversightBadge
|
||||||
|
&& plannerOverseerState
|
||||||
&& plannerOverseerState.state !== "idle"
|
&& plannerOverseerState.state !== "idle"
|
||||||
&& plannerOverseerState.oversightLevel !== "off"
|
&& plannerOverseerState.oversightLevel !== "off",
|
||||||
&& (hasTaskOversightOverride || workflowOversightResolved)
|
|
||||||
&& effectiveOversightLevel !== "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([
|
it.each([
|
||||||
["rejected", () => vi.mocked(fetchWorkflowSettingValues).mockRejectedValueOnce(new Error("unavailable"))],
|
["rejected", () => vi.mocked(fetchWorkflowSettingValues).mockRejectedValueOnce(new Error("unavailable"))],
|
||||||
["missing", () => vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({ stored: {}, effective: {}, orphaned: [] })],
|
["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();
|
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([
|
it.each([
|
||||||
["aggregate", { workflowBadge: { workflowId: "aggregate-active", workflowName: "Aggregate active" } }],
|
["aggregate observe", "observe", { workflowBadge: { workflowId: "aggregate-observe", workflowName: "Aggregate observe" } }],
|
||||||
["selected workflow", { planningWorkflowId: "selected-active" }],
|
["aggregate steer", "steer", { workflowBadge: { workflowId: "aggregate-steer", workflowName: "Aggregate steer" } }],
|
||||||
])("renders the eye only after positively resolved active oversight for %s cards", async (_surface, props) => {
|
["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({
|
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||||
stored: { plannerOversightLevel: "steer" },
|
stored: { plannerOversightLevel: level },
|
||||||
effective: { plannerOversightLevel: "steer" },
|
effective: { plannerOversightLevel: level },
|
||||||
orphaned: [],
|
orphaned: [],
|
||||||
});
|
});
|
||||||
renderCard(staleSnapshot("in-progress"), props);
|
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();
|
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 });
|
Object.defineProperty(window, "innerWidth", { value: 375, configurable: true });
|
||||||
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||||
stored: { plannerOversightLevel: "off" },
|
stored: {},
|
||||||
effective: { plannerOversightLevel: "off" },
|
effective: { plannerOversightLevel: "autonomous" },
|
||||||
orphaned: [],
|
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("planner-overseer-state-badge")).toBeNull();
|
||||||
expect(screen.queryByTestId("card-header-badges")).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