aedee4b8231bf050c3240a00ab6645ede5d87ee9
423 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b26894e0dd |
FN-9095: move spec alignment details to Definition tab bottom
Place low-frequency spec alignment provenance after plan and relationship content.\n\n- Move the Definition-tab spec-lock report to the final section.\n- Cover populated, unavailable, and absent spec-lock report states.\n- Document the placement and add a patch changeset.\n\nFiles changed:\n .changeset/fn-9095-spec-alignment-position.md | 7 ++\n docs/dashboard-guide.md | 2 +-\n .../dashboard/app/components/TaskDetailModal.tsx | 87 +++++++++++-----------\n .../__tests__/TaskDetailModal.spec-lock.test.tsx | 63 ++++++++++++++++\n 4 files changed, 115 insertions(+), 44 deletions(-) Fusion-Task-Id: FN-9095 Fusion-Task-Lineage: c017325c-b7c1-4f0e-abab-49462903a778 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
cc1041207c |
FN-9012: hide empty task Recommendations tabs
Show Recommendations only for completed tasks with task-owned recommendations. - Gate tab visibility and reconciliation on matching task-detail recommendation data. - Cover empty, populated, stale-detail, and task-switch recommendation states. - Document the behavior and add a patch changeset. Files changed: .changeset/fn-9012-recommendations-tab-visibility.md | 7 + docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/TaskDetailModal.tsx | 33 ++- packages/dashboard/app/components/TaskRecommendationsTab.tsx | 2 +- packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx | 305 +++++++++++++++++---- 5 files changed, 289 insertions(+), 60 deletions(-) Fusion-Task-Id: FN-9012 Fusion-Task-Lineage: 2dd3b1c6-ab08-43e3-9c9e-c8d3d9cf4692 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e610c72034 |
FN-8943: reconcile spec-lock divergence history
Preserve prior divergence when a task is re-locked after plan changes. - Persist immutable spec locks, current-plan evidence, and drift reports. - Reconcile retained divergence into re-approved alignment state across engine, API, and dashboard views. - Fence Plan Review acceptance and schema upgrades while retaining migration identity parity. Files changed: .changeset/fn-8845-spec-lock-drift-report.md | 7 + .changeset/fn-8943-spec-lock-divergence.md | 7 + docs/architecture.md | 17 +- docs/dashboard-guide.md | 3 + docs/missions.md | 4 + .../core/src/__tests__/planner/spec-lock.test.ts | 158 ++++++++++++++ .../src/__tests__/postgres/schema-applier.test.ts | 30 ++- .../postgres/task-dependency-mutation.pg.test.ts | 74 +++++++ packages/core/src/index.gate.ts | 4 + packages/core/src/index.ts | 4 + packages/core/src/planner/drift-report.ts | 152 +++++++++++++ packages/core/src/planner/spec-lock.ts | 182 ++++++++++++++++ .../migrations/0050_spec_lock_drift_report.sql | 29 +++ .../0051_spec_lock_source_revision_bigint.sql | 3 + packages/core/src/postgres/schema-applier.ts | 25 ++- packages/core/src/postgres/schema/project.ts | 13 ++ packages/core/src/store.ts | 242 ++++++++++++++++++++- .../core/src/task-store/branch-and-pr-entities.ts | 32 ++- packages/core/src/task-store/project-store-ops.ts | 34 ++- packages/core/src/task-store/task-update.ts | 66 +++++- packages/core/src/task-store/update-task-deps.ts | 31 ++- packages/dashboard/app/api.ts | 3 + packages/dashboard/app/api/tasks/tasks.ts | 22 ++ .../dashboard/app/components/MissionManager.css | 21 ++ .../dashboard/app/components/MissionManager.tsx | 41 +++- .../dashboard/app/components/TaskDetailModal.css | 26 +++ .../dashboard/app/components/TaskDetailModal.tsx | 62 +++++- .../__tests__/TaskDetailModal.spec-lock.test.tsx | 80 +++++++ .../__tests__/TaskDetailModal.test-helpers.ts | 2 + .../src/__tests__/plan-approval-status.pg.test.ts | 81 ++++++- .../src/routes/register-task-workflow-routes.ts | 59 ++++- .../src/__tests__/mission-feature-sync.test.ts | 15 +- .../src/__tests__/spec-drift-reconciler.test.ts | 108 +++++++++ .../engine/src/executor/execute-workflow-graph.ts | 49 ++++- .../engine/src/missions/mission-feature-sync.ts | 55 ++++- packages/engine/src/project-engine.ts | 33 +++ packages/engine/src/spec-drift-reconciler.ts | 105 +++++++++ packages/engine/src/triage.ts | 29 +++ 38 files changed, 1842 insertions(+), 66 deletions(-) Fusion-Task-Id: FN-8943 Fusion-Task-Lineage: 2a7f8a38-99aa-4c4b-8c36-41d14c466d21 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
b181f20859 |
FN-8848: show Recommendations for completed tasks
Keep the completed-task Recommendations destination available when no records exist. - Render a localized empty state without task-creation controls. - Preserve Recommendations selection until a task leaves the completed column. - Cover empty states and shared detail entry points with dashboard tests. - Document the behavior and add a patch changeset. Files changed: .changeset/fn-8848-recommendations-empty-state.md | 7 +++ docs/dashboard-guide.md | 2 +- .../dashboard/app/components/TaskDetailModal.tsx | 15 ++--- .../app/components/TaskRecommendationsTab.css | 5 ++ .../app/components/TaskRecommendationsTab.tsx | 5 +- .../TaskDetailModal.recommendations.test.tsx | 21 +++++++ .../__tests__/TaskDetailModal.summary-tab.test.tsx | 73 +++++++++++++++++++++- packages/i18n/locales/en/app.json | 1 + packages/i18n/locales/es/app.json | 1 + packages/i18n/locales/fr/app.json | 1 + packages/i18n/locales/ko/app.json | 1 + packages/i18n/locales/zh-CN/app.json | 1 + packages/i18n/locales/zh-TW/app.json | 1 + packages/i18n/src/resources.d.ts | 1 + 14 files changed, 125 insertions(+), 10 deletions(-) Fusion-Task-Id: FN-8848 Fusion-Task-Lineage: e200dfba-8113-4ede-a117-f94d221a8e7d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d450dbe971 |
FN-8829: add task recommendations
Add persistent task recommendations that agents can create, resolve, and display in task details. - Persist recommendation state and expose task recommendation API routes. - Generate recommendations from executor task completions with duplicate suppression. - Add localized dashboard recommendation tab and settings control with coverage. Files changed: .changeset/fn-8829-recommendations.md | 7 + docs/dashboard-guide.md | 1 + docs/settings-reference.md | 1 + .../postgres/settings-persistence.pg.test.ts | 10 + .../postgres/task-recommendations.pg.test.ts | 191 +++++++++ .../core/src/__tests__/settings-parity.test.ts | 2 + packages/core/src/config/settings-schema.ts | 2 + packages/core/src/index.ts | 2 +- .../0047_fn_8829_task_recommendations.sql | 3 + packages/core/src/postgres/schema-applier.ts | 32 +- packages/core/src/postgres/schema/project.ts | 2 + packages/core/src/store.ts | 12 +- packages/core/src/task-store/persistence.ts | 4 +- packages/core/src/task-store/serialization.ts | 1 + packages/core/src/task-store/settings-ops.ts | 22 + packages/core/src/task-store/task-mutation-ops.ts | 78 +++- packages/core/src/task-store/task-row-mappers.ts | 2 +- packages/core/src/task-store/task-update.ts | 51 ++- packages/core/src/types.ts | 4 + packages/core/src/types/settings/settings-scope.ts | 6 + packages/core/src/types/task/task-core.ts | 18 + .../__tests__/App.openTasksInRightSidebar.test.ts | 3 +- packages/dashboard/app/__tests__/api-tasks.test.ts | 31 ++ packages/dashboard/app/api/legacy.ts | 1 + packages/dashboard/app/api/tasks/tasks.ts | 24 ++ .../dashboard/app/components/TaskDetailModal.tsx | 43 +- .../app/components/TaskRecommendationsTab.css | 71 ++++ .../app/components/TaskRecommendationsTab.tsx | 127 ++++++ .../__tests__/SettingsModal.general.test.tsx | 11 + .../TaskDetailModal.recommendations.test.tsx | 111 +++++ .../app/components/settings/section-keys.ts | 1 + .../settings/sections/GeneralSection.tsx | 14 + .../settings-default-descriptions.test.tsx | 1 + packages/dashboard/app/hooks/useModalManager.ts | 6 + packages/dashboard/app/plugins/types.ts | 7 +- .../__tests__/task-recommendation-routes.test.ts | 472 +++++++++++++++++++++ .../src/routes/register-task-workflow-routes.ts | 263 +++++++++++- .../executor-task-recommendations.test.ts | 128 ++++++ packages/engine/src/executor.ts | 66 ++- packages/i18n/locales/en/app.json | 15 +- packages/i18n/locales/es/app.json | 16 +- packages/i18n/locales/fr/app.json | 16 +- packages/i18n/locales/ko/app.json | 16 +- packages/i18n/locales/zh-CN/app.json | 16 +- packages/i18n/locales/zh-TW/app.json | 16 +- packages/i18n/src/resources.d.ts | 12 + 46 files changed, 1908 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8829 Fusion-Task-Lineage: 5f60a1fb-9cf8-4577-9bfd-c20a2d402333 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
3dd824d04e |
FN-8823: respect shared member auto-merge holds
Honor project and member auto-merge consent consistently throughout shared-branch integration. - Apply project autoMerge settings and explicit member overrides to shared-member hold decisions. - Expose shared-member integration hold state and controls in branch-group dashboard and lifecycle APIs. - Add regression coverage, operator documentation, and a patch changeset. Files changed: .changeset/fn-8823-shared-member-consent.md | 7 ++ docs/dashboard-guide.md | 4 +- packages/core/src/__tests__/task-merge.test.ts | 57 +++++++++++++++ packages/core/src/index.gate.ts | 3 + packages/core/src/index.ts | 3 + packages/core/src/merge/task-merge.ts | 81 ++++++++++++++++++--- .../dashboard/app/api/tasks/tasks-lifecycle.ts | 11 +++ .../dashboard/app/components/BranchGroupCard.css | 84 ++++++++++++++++++++++ .../dashboard/app/components/BranchGroupCard.tsx | 58 ++++++++++++++- packages/dashboard/app/components/ListView.tsx | 14 +++- .../dashboard/app/components/TaskDetailModal.tsx | 20 +++++- .../components/__tests__/BranchGroupCard.test.tsx | 44 +++++++++++- .../components/__tests__/TaskDetailModal.test.tsx | 24 ++++++- .../app/components/dashboard/MainContent.tsx | 2 +- .../app/components/useRightDockController.tsx | 2 +- packages/dashboard/app/hooks/useModalManager.ts | 6 ++ .../src/__tests__/routes-branch-groups.test.ts | 42 ++++++----- .../src/routes/register-branch-groups-routes.ts | 9 ++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 21 +++--- .../src/__tests__/group-merge-coordinator.test.ts | 7 +- .../workflow-graph-executor-handlers.test.ts | 33 ++++++--- packages/engine/src/executor.ts | 27 ++++--- packages/engine/src/project-engine.ts | 9 ++- packages/engine/src/self-healing.ts | 16 ++--- .../src/workflow-node-runners/merge-runner.ts | 13 ++-- 25 files changed, 503 insertions(+), 94 deletions(-) Fusion-Task-Id: FN-8823 Fusion-Task-Lineage: 19a8ed3f-26e1-4c8e-8782-ca366718a3f2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
cdf81a2244 |
fix(FN-8826): retry partial workflow progress after restart
Fusion-Task-Id: FN-8826 |
||
|
|
55e8484418 | fix(FN-8764): refresh stale task activity feed | ||
|
|
6ccde2b0d8 |
FN-8806: stabilize task title expansion during resize
Keep expanded task titles stable while their container resizes. - Measure persistent title text separately from the interactive expansion control. - Fence hidden pop-out resize observers and preserve expansion state across detail hosts. - Add rendering and browser coverage for resize-driven title behavior. Files changed: .changeset/fn-8806-task-title-flicker.md | 7 + .../dashboard/app/components/TaskDetailModal.css | 16 +- .../dashboard/app/components/TaskDetailModal.tsx | 37 +-- .../__tests__/TaskDetailModal.rendering.test.tsx | 42 +++- .../app/task-modal-touch-resize-e2e-fixture.tsx | 149 +++++++++++- .../task-modal-touch-resize-browser.test.ts | 267 ++++++++++++++++++++- 6 files changed, 485 insertions(+), 33 deletions(-) Fusion-Task-Id: FN-8806 Fusion-Task-Lineage: 9519a5dc-ae6d-43e1-a0da-7ac963f27d7f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
4324762d9a |
FN-8805: preserve expanded task titles on resize
Keep task-detail title expansion stable across resize observers and task changes. - Measure overflow only in the collapsed title layout. - Preserve explicit expand/collapse selection through stale resize callbacks. - Cover modal and embedded narrow-title behavior with regression tests. - Add a patch changeset for the title stability fix. Files changed: .changeset/fn-8805-stable-task-title.md | 7 ++ packages/dashboard/app/components/TaskDetailModal.tsx | 39 +++++--- packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx | 110 +++++++++++++++++++++ 3 files changed, 141 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-8805 Fusion-Task-Lineage: 66177622-78e8-4378-88f6-c0979a96afd2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
59cfa47b1c |
FN-8801: refresh task state after lifecycle actions
Refresh dashboard task views immediately when lifecycle mutations are confirmed. - Centralize confirmed task reconciliation across pause and unpause actions - Propagate lifecycle handlers through dashboard detail and dock hosts - Add state synchronization regression coverage and a patch changeset Files changed: .changeset/fn-8801-immediate-unpause-state.md | 7 ++ packages/dashboard/app/App.tsx | 6 +- packages/dashboard/app/components/AppModals.tsx | 4 ++ packages/dashboard/app/components/ListView.tsx | 2 + .../dashboard/app/components/TaskDetailModal.tsx | 26 +++++--- .../TaskDetailModal.definition-actions.test.tsx | 76 +++++++++++++++------ .../app/components/dashboard/MainContent.tsx | 2 + .../app/components/useRightDockController.tsx | 4 ++ .../dashboard/app/hooks/__tests__/useTasks.test.ts | 57 ++++++++++++++++ packages/dashboard/app/hooks/useTasks.ts | 77 +++++++++------------- 10 files changed, 183 insertions(+), 78 deletions(-) Fusion-Task-Id: FN-8801 Fusion-Task-Lineage: 2ab80d4d-8914-43c5-8ebb-281d31276b61 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
35810666be |
FN-8797: stabilize task detail refreshes
Keep task-detail state and controls mounted while Definition content refreshes. - Add a prompt-only task endpoint and client request path. - Preserve task lifecycle, workflow badges, and actions during prompt refreshes and workflow updates. - Cover prompt refresh stability, late responses, and route behavior. Files changed: packages/dashboard/app/__tests__/api-tasks.test.ts | 14 ++ packages/dashboard/app/api/legacy.ts | 3 + packages/dashboard/app/api/tasks/tasks.ts | 15 ++ .../dashboard/app/components/TaskDetailModal.tsx | 102 ++++++---- .../__tests__/TaskDetailModal.rendering.test.tsx | 211 ++++++++++++++++++--- .../__tests__/TaskDetailModal.test-helpers.ts | 1 + ...gister-task-workflow-routes.task-prompt.test.ts | 93 +++++++++ .../src/routes/register-task-workflow-routes.ts | 17 ++ 8 files changed, 388 insertions(+), 68 deletions(-) Fusion-Task-Id: FN-8797 Fusion-Task-Lineage: 57d571b4-4a02-4c96-9986-444c525d8405 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
fa3df0f6bd |
FN-8798: synchronize planning status across task views
Keep board, list, and task detail planning indicators consistent with fresh lifecycle state. - preserve authoritative task snapshots across SSE and detail refreshes - render transient replan planning only for fresh, unpaused planner activity - pass global pause state to every task-detail host and cover status convergence Files changed: packages/dashboard/app/App.tsx | 2 + packages/dashboard/app/components/AppModals.tsx | 3 + packages/dashboard/app/components/ListView.tsx | 11 +- packages/dashboard/app/components/TaskCard.tsx | 7 +- .../dashboard/app/components/TaskDetailModal.tsx | 27 ++- .../app/components/__tests__/AppModals.test.tsx | 201 +++++++++++++++++++-- .../app/components/__tests__/ListView.test.tsx | 31 ++++ .../app/components/__tests__/TaskCard.test.tsx | 17 ++ .../app/components/dashboard/MainContent.tsx | 1 + .../__tests__/useTasks-hydration-freshness.test.ts | 58 +++++- .../dashboard/app/hooks/__tests__/useTasks.test.ts | 29 ++- packages/dashboard/app/hooks/useTasks.ts | 51 +++++- .../utils/__tests__/taskStatusBadgeLabel.test.ts | 22 ++- .../dashboard/app/utils/taskStatusBadgeLabel.ts | 36 ++++ 14 files changed, 453 insertions(+), 43 deletions(-) Fusion-Task-Id: FN-8798 Fusion-Task-Lineage: 7defae4e-fb39-4d2c-943a-ef6c1a164bfc Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1320265455 |
FN-8796: stabilize task-detail lifecycle snapshots
Keep open task details on their newest lifecycle state during scheduler resyncs. - Reconcile task snapshots by lifecycle timestamps across modal, panel, dock, list, and popup hosts. - Preserve fetched detail fields while accepting only fresher status and column transitions. - Cover stale queued dependency and file-overlap payload regressions. Files changed: packages/dashboard/app/App.tsx | 7 +- packages/dashboard/app/components/AppModals.tsx | 16 +--- packages/dashboard/app/components/ListView.tsx | 9 +- .../dashboard/app/components/TaskDetailModal.tsx | 33 +++---- .../app/components/__tests__/AppModals.test.tsx | 59 ++++++++++++ .../__tests__/TaskDetailModal.rendering.test.tsx | 45 +++++++++ .../app/components/dashboard/MainContent.tsx | 8 +- .../app/components/useRightDockController.tsx | 4 +- .../__tests__/useTasks-hydration-freshness.test.ts | 49 +++++++++- packages/dashboard/app/hooks/useModalManager.ts | 3 +- packages/dashboard/app/hooks/useTasks.ts | 103 +++++++++++++-------- 11 files changed, 252 insertions(+), 84 deletions(-) Fusion-Task-Id: FN-8796 Fusion-Task-Lineage: 430b2c46-717d-4b83-803f-6574cf64670d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
2af01dd27d |
FN-8786: make task titles directly expandable
Make overflowed task titles directly expandable with an accessible title control. - Replace the standalone Show more/Show less row with a title-owned button. - Add focus styling while preserving title clamping and summarization behavior. - Cover title expansion, keyboard access, fallbacks, and responsive styling. Files changed: .../dashboard/app/components/TaskDetailModal.css | 35 ++--- .../dashboard/app/components/TaskDetailModal.tsx | 26 ++-- .../__tests__/TaskDetailModal.rendering.test.tsx | 150 +++++++++++---------- 3 files changed, 112 insertions(+), 99 deletions(-) Fusion-Task-Id: FN-8786 Fusion-Task-Lineage: 351a9f33-42c0-4a4f-a34a-94aa726f2436 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7b1c8db1d3 |
FN-8779: keep Activity Feed scrolling above footer
Keep Feed history scrollable within Task Detail while the action footer remains fixed. - Add a Feed-specific flex and overflow layout for Activity history. - Keep Task Detail footer and modal controls outside the scrolling body. - Cover Feed footer containment and responsive layout behavior. Files changed: packages/dashboard/app/components/TaskDetailModal.css | 38 +++++++++++++-- packages/dashboard/app/components/TaskDetailModal.tsx | 12 ++--- ...etailModal.responsive-and-dependencies.test.tsx | 46 ++++++++++++++++++ .../TaskDetailModal.task-activity-chat.test.tsx | 54 ++++++++++++++++++++++ 4 files changed, 139 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-8779 Fusion-Task-Lineage: 4bc4851c-27a1-4b45-afdb-11ccbc8a017e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9939897aab |
fix: prevent stale planning approvals and review churn (#3327)
## Summary Planning can no longer approve or execute against evidence from a superseded dependency episode. Dependency mutations, approval decisions, recovery, and execution admission now share serialized lifecycle rules, so stale planner work cannot restore an invalid approval or release an unplanned task. Review also converges instead of discovering one blocker per round. Planning performs a repository-grounded completeness pass up front; Plan Review batches all independently discoverable blockers and carries an episode-scoped decision ledger across revisions; code review traces changed invariants through production consumers and tests. Repeated feedback still advances the safety budget, while provider failures and superseded episodes stay outside the remediation ledger. The dashboard now exposes manual approval only for the intended exhausted-review state, and refusal/recovery audit events make rejected lifecycle transitions diagnosable without leaking prompt content. ## Validation - `pnpm verify:fast` — scoped typechecks/builds, CLI build, and boot smoke passed. - Focused Core and Engine regression suites — 511 tests passed. - `pnpm lint`, strict changeset validation, Core/Engine typechecks, and package builds passed. Fixes #3325. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved Plan Review approvals, rejections, and replan-cap handling across task workflows. * Added cumulative feedback and attempt tracking across repeated planning reviews. * Added safer recovery for stalled planning handoffs and interrupted approval updates. * **Bug Fixes** * Prevented stale approvals and unplanned execution after dependency changes. * Improved concurrent approval handling, retryability, and refusal-record deduplication. * Refined dashboard approval indicators and responsive approval views. * **Quality Improvements** * Strengthened planning and code-review completeness checks and blocking-finding coverage. * Preserved review history while clearly marking outdated approvals. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
cb57093d03 |
refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary Wave 17 organizes Fusion into **domain folders** (stacks on #2397). ### Layout - **core/types/** — board, task, agents, settings, merge, workflow, mesh, … - **core/src/** — agents, ai, async-stores, workflows, tasks, config, db, … - **dashboard/app/api/** — client, tasks, agents, git, missions, planning, … - **engine/src/** — agents, auth, execution, merge, missions, overseer, worktree, … Root keepers retained for large entrypoints (`store.ts`, `executor.ts`, `merger.ts`, …). Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy) stay stable. ## Test plan - [x] `@fusion/core` typecheck - [x] `@fusion/engine` typecheck (pre-existing playwright-core noise only) - [ ] CI merge gate **Stack:** #2394 → #2397 → **this PR** |
||
|
|
da17a57bde |
FN-8731: refresh task definition prompts during planning
Keep the visible task definition current while planning and Plan Review can revise it. - Refresh Definition prompts when the visible tab opens and poll during active planning lifecycles. - Preserve inline edit drafts while applying authoritative prompt updates. - Add lifecycle and hidden-host coverage, documentation, and a patch changeset. Files changed: .changeset/fn-8731-definition-prompt-refresh.md | 7 ++ docs/dashboard-guide.md | 1 + .../dashboard/app/components/TaskDetailModal.tsx | 106 ++++++++++++++++++--- .../TaskDetailModal.definition-actions.test.tsx | 91 +++++++++++++++++- .../TaskDetailModal.popup-hidden-gating.test.tsx | 25 ++++- 5 files changed, 215 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-8731 Fusion-Task-Lineage: b5536ad7-cbe1-42ee-a6df-6e997eeeda05 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
bf173dad7c |
FN-8696: keep reverted tasks out of Done
Keep reverted tasks out of completed views and give every task-detail host a revision recovery path. - Exclude reverted tasks from complete columns and group them in dedicated recovery lists. - Provide Delete and Revise actions in cards, lists, docks, modals, and popped-out details. - Add localized copy, documentation, regression coverage, and a patch changeset. Files changed: .changeset/fn-8696-reverted-task-resolution.md | 7 ++++ docs/dashboard-guide.md | 4 ++ packages/dashboard/app/App.tsx | 2 + packages/dashboard/app/components/AppModals.tsx | 2 + packages/dashboard/app/components/Board.tsx | 42 +++++++++++++++++++- packages/dashboard/app/components/DockTaskList.tsx | 21 +++++++++- packages/dashboard/app/components/ListView.tsx | 18 +++++++++ packages/dashboard/app/components/TaskCard.css | 4 ++ packages/dashboard/app/components/TaskCard.tsx | 9 +++++ .../dashboard/app/components/TaskDetailModal.tsx | 18 ++++++++- .../app/components/__tests__/Board.test.tsx | 46 ++++++++++++++++++++++ .../app/components/__tests__/DockTaskList.test.tsx | 29 +++++++++++++- .../app/components/dashboard/MainContent.tsx | 4 ++ .../app/components/overflowViewRegistry.tsx | 3 ++ .../app/components/useRightDockController.tsx | 9 +++++ .../app/utils/__tests__/taskRevert.test.ts | 16 +++++++- packages/dashboard/app/utils/taskRevert.ts | 18 +++++++++ packages/i18n/locales/en/app.json | 3 ++ 18 files changed, 249 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-8696 Fusion-Task-Lineage: 7c9550fb-ef8e-4422-8ec1-0de1665413a3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
0e8f6769ba |
FN-8666: add credential instance pickers
Add credential-instance selection across dashboard model configuration surfaces. - Expose provider credential instances in custom model dropdowns and task, workflow, and settings forms. - Persist per-role credential-instance overrides through dashboard APIs and effective model resolution. - Document credential-instance precedence and cover dropdown/settings behavior, including task state synchronization. Files changed: .changeset/fn-8666-credential-instance-picker.md | 7 ++ docs/settings-reference.md | 2 + docs/workflow-steps.md | 2 +- packages/dashboard/app/api.ts | 1 + packages/dashboard/app/api/models-usage.ts | 27 ++++- packages/dashboard/app/api/tasks.ts | 8 ++ .../app/components/CustomModelDropdown.css | 12 ++- .../app/components/CustomModelDropdown.tsx | 71 ++++++++++++- .../dashboard/app/components/InlineCreateCard.tsx | 23 +++- packages/dashboard/app/components/ListView.tsx | 29 +++++- .../app/components/ModelSelectionModal.tsx | 25 ++++- .../dashboard/app/components/ModelSelectorTab.tsx | 29 +++++- packages/dashboard/app/components/NewTaskModal.tsx | 23 +++- .../dashboard/app/components/QuickEntryBox.tsx | 28 +++++ .../dashboard/app/components/SettingsModal.tsx | 2 + .../dashboard/app/components/TaskDetailModal.tsx | 32 +++++- packages/dashboard/app/components/TaskForm.tsx | 18 ++++ .../app/components/WorkflowNodeEditor.tsx | 13 ++- .../app/components/WorkflowSettingsPanel.tsx | 38 ++++++- ...ustomModelDropdown.credential-instance.test.tsx | 111 ++++++++++++++++++++ .../__tests__/WorkflowSettingsPanel.test.tsx | 9 +- .../app/components/effective-model-resolution.ts | 8 +- .../settings/sections/GlobalModelsSection.tsx | 31 +++++- .../settings/sections/ProjectModelsSection.tsx | 116 ++++++++++++++++----- .../ProjectModelsSection.chatDefault.test.tsx | 33 +++++- packages/dashboard/app/hooks/useFavorites.ts | 6 +- packages/dashboard/app/hooks/useModelsCache.ts | 5 +- .../src/routes/register-task-workflow-routes.ts | 17 ++- 28 files changed, 655 insertions(+), 71 deletions(-) Fusion-Task-Id: FN-8666 Fusion-Task-Lineage: 6c3711b3-68b8-47a0-ac36-4b64846adf39 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ec55889605 |
FN-8674: add plan approval banner actions
Place plan approval controls alongside the task approval message for faster operator action. - Add approve and reject controls to the plan-approval banner. - Reuse existing approval handlers and add coverage for banner and footer actions. - Add responsive banner action styling and a patch changeset. Files changed: .changeset/plan-approval-banner-actions.md | 7 +++ .../dashboard/app/components/TaskDetailModal.css | 14 +++++ .../dashboard/app/components/TaskDetailModal.tsx | 19 ++++++- .../TaskDetailModal.definition-actions.test.tsx | 59 +++++++++++++++++----- 4 files changed, 85 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-8674 Fusion-Task-Lineage: 89c521c2-535d-4492-8b39-6e1517693218 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
c0ddadd746 |
FN-8634: balance Task Detail and terminal right padding
Balance perceived right-edge spacing across Task Detail and terminal shells. - Move content padding outside scrollbar-owning Task Detail surfaces. - Account for injected terminal viewport scrollbar tracks across modal variants. - Add layout regression coverage and a patch changeset. Files changed: .changeset/fn-8634-task-detail-right-padding.md | 7 + .../__tests__/detail-body-mobile-overflow.test.ts | 8 +- .../__tests__/task-detail-inset-symmetry.test.ts | 271 +++++++++++---------- .../dashboard/app/components/SessionTerminal.css | 16 +- .../dashboard/app/components/SessionTerminal.tsx | 14 +- .../dashboard/app/components/TaskDetailModal.css | 49 ++-- .../dashboard/app/components/TaskDetailModal.tsx | 48 ++-- .../dashboard/app/components/TerminalModal.css | 11 +- .../components/__tests__/TaskChangesTab.test.tsx | 23 +- ...etailModal.responsive-and-dependencies.test.tsx | 16 +- 10 files changed, 273 insertions(+), 190 deletions(-) Fusion-Task-Id: FN-8634 Fusion-Task-Lineage: 58cce580-5da0-47cf-a325-20b3707dbeef Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
40e64468d2 |
fix(dashboard): GitHub tracking was unreachable on a renamed board — a defect class the census cannot see (#3149)
The census backlog is verified-exhausted (12 guards, every blocker re-checked in #3082). This is from the class **the census structurally cannot count**, and it is a real capability loss. ## The defect ```ts const GITHUB_TRACKING_EDITABLE_COLUMNS: Set<ColumnId> = new Set<ColumnId>(["triage", "todo", "in-progress", "in-review", "ideas"]); function canTaskEditGithubTracking(column, workflowId) { return GITHUB_TRACKING_EDITABLE_COLUMNS.has(column) || workflowId === CODING_IDEAS_WORKFLOW_ID; } ``` No resolved branch, no flags fallback. On a board whose lanes are renamed this matched **nothing**, so the helper returned `false` for every task and `showGithubTrackingSection` hid the section outright. **The operator could not turn GitHub tracking on or off** — no error, no explanation, the affordance simply absent. The only thing keeping it reachable was the unrelated `builtin:coding-ideas` escape hatch on the right-hand side. ## Why no gate saw it, and why this class matters now The census counts **comparisons** against legacy ids. This is a **Set literal — a definition** — consulted with `.has()`. Nothing in the backlog ever pointed here. It is the same blind spot that hid `TIME_INDICATOR_COLUMNS` and `BLOCKER_ESCALATION_COLUMNS`, both of which were also found by hand rather than by any gate. I found it by scanning for legacy-id **collections that gate a live column value**, rather than for comparisons: **19 such sites** across the tree. Most are already correct — either `if (!flags) return LEGACY_…has(column)` fallbacks, or seed-then-add resolved sets (`agent-reflection.ts`, `ephemeral-worker-manager.ts`). This one had neither. With the comparison backlog at 12 and every remaining entry blocked or documented, **this is where the remaining renamed-board defects actually live.** ## The fix The set's meaning is "not finished" — every lane except complete and archived — which is what the roles now express: ```ts if (workflowId === CODING_IDEAS_WORKFLOW_ID) return true; if (!columnFlags) return GITHUB_TRACKING_EDITABLE_COLUMNS.has(column); // unchanged pre-fetch return !isCompleteColumnRole(columnFlags, column) && !isArchivedColumnRole(columnFlags, column); ``` The caller passes `detailColumnFlags` — the **task-identity-guarded** value. `workflowMoveMetadata` outlives a task switch, and this file's own `2026-07-30-17:30` note records **six** review findings from consumers that read around that guard. Passing the unguarded value would answer about the previous card's workflow: worse than the legacy fallback, because it is confidently wrong rather than merely stale. ## Verification | | result | |---|---| | suite | **3 passed** | | mutation (restore the literal) | **1 failed \| 2 passed** — the renamed-WIP case only | | dashboard `tsc -p tsconfig.app.json` | **0 errors** | | census `--strict` | exit 0, **unchanged** — this class is invisible to it | The test drives the **production path** (`fetchBoardWorkflows` → `resolveTaskWorkflowMetadata` → `currentColumnFlags`) rather than injecting flags as props, so it covers the producer as well as the consumer. `building` and `shipped` collide with no legacy id, so a surviving `.has(column)` cannot pass by luck; the `todo` control pins that the default vocabulary is unaffected, and the renamed-COMPLETE negative pins that the fix does not hand editability to a finished card. Note: `tsconfig.test-check.json` fails on `main` as well — pre-existing, and **zero** of its errors come from this branch's files. ## Suggested follow-up The remaining 17 collection sites deserve the same pass, and the scan that found this should probably become a gate — a census that counts comparisons will keep reporting zero while this class quietly grows. I have not built that here because the existing gates already need `#3136`'s attention first, and adding a sixth advisory check that nobody blocks on would repeat the pattern this session keeps running into. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a8dae03fdb |
fleet(dashboard): taskRevert 2 → 0 — the recorded blocker named the wrong variable (#3129)
The largest remaining census cluster. Deferred twice, with a blocker that turns out to be false **in the same component where its counter-example already lives**. ## What the earlier notes got right `detailColumnFlags` describes the **modal's own task**, and the column classified here belongs to a **neighbour**. Supplying it would answer *"is this neighbour finished?"* with a different row's traits — wrong on data, not merely stale on vocabulary. That reasoning stands and I kept it. An earlier pass also converted this, left the parameter unsupplied, and **reverted it** — correctly. An unsupplied optional parameter is strictly worse than the literal: the guard is gone, the census counts a conversion, and the behaviour is the legacy fallback forever. That rule is why the wiring ships in this same commit. ## What the conclusion got wrong > "A correct conversion needs per-**neighbour** flags — which the modal does not have and should not fetch mid-render." `columnFlagsByTaskId` is a per-task map. It is **already a prop** of `TaskDetailModal` (declared :367, destructured :727), and the call site at :992 sits **below** that destructure. And `TaskDetailModal` already uses it exactly this way, for the near-duplicate canonical: ```ts columnFlagsByTaskId?.get(nearDuplicateCanonical.id) ``` …under a note observing that *its* blocker had been *"asserted from the shape of the problem rather than tested against what was in scope."* Same assertion, one function over. So the supplier the earlier note went looking for exists, is per-neighbour, and needs no fetch. ## What it fixes This lookup skips **finished** candidates so a done/archived prior undo attempt never renders as an active "Undo task" link. On a board that renames those lanes it matched neither — a finished undo task kept rendering as open, which is precisely the stale affordance the function's own header says it exists to prevent. ## Census | | before | after | |---|---|---| | `taskRevert.ts` | 2 | **0** | | repo backlog | 17 | **15** | ## Measured - 4 new cases; `taskRevert.test.ts` **11/11 pass**. - **MUTATION**: restoring the literal pair fails the renamed case. - **The negative is load-bearing.** The map is fail-soft, so a candidate it does not cover must still be treated as **open**, not skipped. A conversion that skipped unknown candidates would *hide live undo links* — failing in the direction nobody reports. - A **control** pins that an unwired caller (no flags at all) still skips the legacy ids, so the optional parameter cannot regress default boards. - `TaskDetailModal` suites — **31 files / 664 tests pass**. - `tsc --noEmit -p tsconfig.app.json` clean; census `--strict`, `check-lane-wiring`, `check-fnxc-future-dates` clean. ## Pattern worth noting This is the fourth deferral this session whose stated blocker had dissolved or misidentified itself, and the second where the counter-example was already in the same file. The common shape: a note records *why* something is blocked, is accurate when written, and is never re-checked — so the block outlives its cause. Re-reading them cost minutes each and returned two real conversions. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e9b24b69e8 |
fix(dashboard): the duplicate banner judged the canonical by legacy lane ids (#3032)
Found by applying the check I proposed on #3028: **re-test each `ALLOWED_OMISSIONS` entry — does the omission still fail once the excuse is removed?** It found a stale excuse on its first run. ## The entry's blocker was never tested ``` "…TaskDetailModal.tsx::isNearDuplicateCanonicalInactive" reason: "…Correct supply needs a fetch — a data change. See the note at the site." ``` The reasoning gets the hard part right: passing `detailColumnFlags` would answer about the **modal's** task, not the canonical, and would type-check while reading as a conversion. Rejecting that is correct. Then it concludes the seam needs a fetch — without checking what is in scope. - `columnFlagsByTaskId` is **already a prop of this component** (declared `:367`, destructured `:727`, used for the fan-out map at `:3718`), keyed by task id. - The canonical is `tasks.find((c) => c.id === nearDuplicateOf)` — drawn from the same loaded set the map covers. If the banner can render at all, the canonical is in `tasks`. So `columnFlagsByTaskId?.get(canonical.id)` is the canonical's own flags, no fetch. **`Column.tsx:307` already does exactly this**, with a comment making the same point about not reusing the row's flags — a sibling call site of the same function, solved. ## What was broken The banner's "this duplicates X" warning stayed up when the canonical had landed in a **renamed** complete lane, because `isNearDuplicateCanonicalInactive` fell back to the legacy ids and never saw it as finished. Same user-visible symptom #2997 fixed for the card chip; this is the modal. ## Verification | state | result | |---|---| | clean | seams gate exit 0; 123/123 across `TaskDetailModal.rendering` + `Column.neardup-flags-arrival` | | revert the supply | **gate exit 1** — `isNearDuplicateCanonicalInactive() — supplied by 10/11 call sites; omitted at TaskDetailModal.tsx:1 (of 2)` | That mutation is the point: with the allow-list entry present, this exact omission passed silently. It is now defended by the gate rather than excused by it. `tsc -p tsconfig.app.json` 0 errors in the file, lint clean, FNXC gate exit 0. ## The general point This is the second allow-list entry in two PRs whose stated blocker was wrong — #3028 removed the other one (*"needs a published-API change"*; the SDK is `private: true` and every consumer was in-repo). An `ALLOWED_OMISSIONS` entry is a deferral **carrying a gate's authority**. It reads as settled, it lives inside the checker, and it turns "nobody tested this" into "someone tested it and concluded no". A stale baseline *number* invites a recount; a stale *paragraph* invites agreement. Both entries this gate carried were stale, and the note at this call site had even been revised once — the revision corrected which flags were wrong to pass, and left the untested "needs a fetch" conclusion standing. Worth a periodic sweep of the remaining entries as they accumulate; with these two gone the list is empty, so the cheapest time to institutionalise it is now. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1de0141ab8 |
fix(dashboard): Task Detail's blocking count read the LEGACY lanes (last of the three fan-out surfaces) (#3004)
Third and last of the three surfaces calling the blocker fan-out wrapper, completing the sweep started in #2990 (Board + Executor bar). ## What was wrong, precisely The dependent **list** is lane-independent — core pushes `dependentIds` without consulting lanes — so this section looked broadly right. Two things beside it are not: - `overlapBlockedTodoCount`, rendered as **"FN-X is blocking N todo task(s) via blockedBy overlap"** — counted against the literal `todo`, so on a renamed board it read **0 while cards were genuinely blocked**. - the `stale` marker on each blocking dependent — decided against `terminal`/`review` lanes the operator does not use. A wrong number sitting beside a right list is the easiest kind to miss, which is why I checked what the modal actually consumes before deciding this was worth a PR rather than assuming the whole section was broken. ## Why a prop and not a hook This was the surface I deferred in #2990 because it had no trait index in scope. Two options: - `useBoardWorkflows` inside the modal — rejected. The hook documents that it does **not** dedupe across consumers: each call installs its own visibilitychange/focus listeners and its own SSE subscription. That is a new fetch and subscription per modal open, to answer a question the app has already answered. - **Thread the index that already exists** — `App` builds `footerColumnFlagsByTaskId` for the footer; this forwards it through `AppModals` as an optional prop. Chosen. Optional throughout: a card with no entry keeps the documented legacy fallback, so the remote-node case (where local workflow metadata must never be applied to foreign ids) and the pre-load window stay byte-identical. ## Reverted The new case fails on the rendered text — the modal cannot find `"FN-B is blocking 2 todo task(s) via blockedBy overlap"`. The pre-existing legacy-column case above it passes either way, because `todo` satisfies the literal default; that is exactly why it never caught this. ## Verification TaskDetailModal.rendering + ExecutorStatusBar + useBlockerFanout **206 passed** · dashboard app suite 11986 passed / 5 skipped (581 files) · `pnpm test:gate` 161 + 13 + 487 + 71 · lint · census `--strict` · lane-wiring · fnxc-dates · changesets — green. ## One note for whoever owns the FNXC gate `check-fnxc-future-dates.mjs` **rewrites its baseline as a side effect and still exits 0**. Today's date roll dropped 183 stamps out of "future", so any run dirties `scripts/lib/fnxc-future-dates-baseline.json` in the working tree. It cost me a stash conflict before I noticed. Not bundled here — it is repo-wide midnight drift, not this change — but a check that mutates tracked state on a read is worth a look. |
||
|
|
4f929acc10 |
fix(dashboard): stop over-aggressive component unmounts (keep-alive for planning, terminals, popups) (#2420)
Implements
docs/plans/2026-07-22-001-fix-dashboard-remount-churn-plan.md: every
confirmed source of unnecessary unmount/remount churn in the dashboard,
plus a keep-alive layer for conversation- and terminal-bearing surfaces.
## What changed
**Keying / component identity (U1–U3)**
- Streaming chat segment key no longer embeds `entries.length` — an
expanded thinking block stays expanded while entries stream into it
(R1).
- Dock task list keys `TaskCard` rows by `task.id` (occurrence suffix
only for the duplicate-id anomaly) instead of `id-index` — no remount on
reorder/filter/status change (R2).
- `ProviderStatusBadge` / `GitHubStatusBadge` hoisted out of
ModelOnboardingModal's render body (R3); MCP server rows key by
`server.name` alone (R4).
**Keep-alive layer (U4–U6)**
- New shared `KeepAliveView` wrapper: visible = in-flow flex child;
hidden = out-of-flow `position:absolute; inset:0` with
`visibility:hidden; pointer-events:none` + `aria-hidden` (never
`display:none`, so xterm geometry never collapses).
- Planning Mode renders as a kept-alive sibling of the MainContent
switch after first open (per-project latch mirroring Quick Chat). While
hidden, the session-list SSE, recovery poll, and elapsed ticker suspend
via a new `active` prop; reveal re-subscribes and refreshes the sessions
list once. Payload-carrying entry points (initial-plan handoff, resume)
and project switches remount via a new
`modalManager.planningEntryGeneration` key, preserving pre-keep-alive
fresh-open semantics. `recordResumeEvent` instrumentation records
`remount` on first activation and `route-active` on reveal.
- Task-detail Terminal / Worktree-terminal / Planner-chat tabs stay
mounted-but-hidden after first open (per-task latches; task switch/close
still disposes fully). `SessionTerminal` gains `active`: reveal refits +
forces a font remeasure, and if the WS died while hidden it re-runs the
full attach lifecycle (dead-socket recovery).
- Popped-out task windows hide via FloatingWindow `hidden` instead of
leaving the render array; `TaskDetailContent` gains `active` so hidden
popups close their SSE/EventSource channels while the terminal WS stays
open. `visiblePoppedOutTaskEntries` remains the Escape-shortcut
consumer.
**Planning Mode internal-transition audit (U7)**
- Audit findings: session-list mode and mobile list/detail flips are
CSS-class transitions over one always-mounted detail pane (no
state-discarding unmounts); re-selecting the active session is an
early-return visibility restore; session switching intentionally reloads
from the session row (stream re-attach for generating sessions);
remaining index keys are on stateless lists. No product-code defects
found; regression tests now lock the always-mounted invariant on desktop
+ mobile.
**Cheap-view state (U8)**
- CommandCenter (active sub-tab + date range) and DevServerView
(selected script/task + typed-but-unsent command) persist per project
via `modalPersistence` and restore after their (intentional) unmount
round-trips. Also fixed the candidate auto-fill effect clobbering a
customized non-empty command.
## Symptom Verification
- **Original symptom:** streaming thinking blocks collapsed mid-stream;
terminals reconnected and lost scroll/input on tab flips; Planning Mode
lost in-flight interviews on navigation; popped-out windows vanished
off-view; dock cards remounted on reorder.
- **Exact reproduction:** (1) expand a thinking block during a stream;
(2) run a command in the Terminal tab, flip to Plan and back; (3) start
a planning interview, navigate Board and back; (4) pop out a task with
board/list-only scoping and switch views; (5) change a dock task's
status.
- **Assertion it is gone:** component-identity/instrumentation tests in
TaskChatTab, SessionTerminal, TaskDetailModal
(worktree/planner-chat/tabs), PlanningModeModal keep-alive +
internal-transitions, App keep-alive round-trip, and
App.taskPopupViewGating assert no remount and preserved state for each
repro, across desktop and mobile breakpoints.
## Verification
- File-scoped vitest: 23 files / 1091 tests green (all touched suites
plus FloatingWindow, TerminalModal, TaskPlannerChatTab,
lazy-loaded-views guard, App suites).
- `pnpm verify:fast`: PASS (13 steps — scoped typecheck/build, CLI
build, boot smoke).
- `pnpm check:changesets`: passes; changeset
`fix-dashboard-remount-churn` (`@runfusion/fusion` patch, labeled
format).
- Known pre-existing failures NOT caused by this branch (verified
failing at base
|
||
|
|
ba40942a10 |
batch-dashboard-app: 75 → 2 across packages/dashboard/app — the last two are deliberate, not missed (#2772)
**Batch branch is live: `batch-dashboard-app`.** Push conversions here as commits rather than opening per-file PRs — that is the CI-run bottleneck this model removes. **One-line ownership note for you to arbitrate:** you have addressed me as U11, U12 and U7 at different points, so the `u12 worker -> batch-dashboard-app` mapping is ambiguous from my side. I claimed it because `dashboard/app` is where I have done the most work this session (TaskContextMenu, Column, TaskCard, TaskDetailModal, columnRoles, taskActivity) and I know which of its guards are load-bearing fallbacks. **If another worker is the intended owner, say so and I will hand the branch over rather than both of us pushing to it** — two workers on one shared branch is exactly what silently discarded a reviewed fix in #2645 today. ## The work order (measured at branch point, tests excluded) **75 guards across 32 files.** Largest: `TaskContextMenu.tsx` 9 · `Column.tsx` 7 · `ListView.tsx` 6 · `TaskDetailModal.tsx` 4 · then a long tail of 3s, 2s and 1s. Full per-file list is in the committed work order so feeders can claim without re-measuring. ## Two rules this surface keeps tripping on **1. A literal after `??`, or in the `else` of a `flags ?` ternary, is a DEGRADED-MODE answer — not an unconverted guard.** Two real states reach it: the **pre-load window** (board renders before the workflows fetch resolves) and a card stranded on an id its workflow no longer declares. In both, `columnFlagsById` has no entry at all. Deleting the fallback does not remove a decision — it substitutes "no role" silently, and affordances vanish during first paint. Those sites reach 0 by **marking**, not deleting. Expect `TaskContextMenu.tsx` and the `utils` files to be **mostly marks**. A "9 → 0" that deleted 9 fallbacks is a regression wearing a green census. **2. A marker excuses ONLY the construct it is attached to** — the statement or function holding the literal, not a sibling declaration. This has cost three passes, two of them mine; my first attempt on `reliability-metrics.ts` scored **1 of 6**. **Verify by the count moving, not by the comment existing.** With the ratchet gate-blocking, a mis-marked batch either wedges the gate or locks the miss into a re-recorded baseline. ## Status Opening commit is the work order only — **0 of 75 converted so far.** I am near the end of my context, so I am establishing the branch and the shared list rather than starting conversions I cannot finish cleanly. Feeders can begin immediately; I will keep the branch rebased. My other PR **#2762** (`live-agent-count.ts` 6 → 0) is green and unconflicted — per your rule it should land rather than fold into a batch, and it is `packages/core` so it belongs to batch-core anyway. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Task UI now resolves workflow “column roles” per task to drive diffs/merge details, routing/steering, progress/runtime visibility, and review badges. * Right-dock/overflow views and dev-server now use per-task column traits for “executing” behavior and dependency-based “Up Next” eligibility. * **Bug Fixes** * Fixed bulk action selection/delete/archive eligibility and prevented cross-workflow role leakage. * Made in-review/stale-paused-review, stuck, and effective executor/validator model logic role-aware. * **Tests** * Added regression coverage for degraded-flag behavior and ensured resolved-flag props aren’t ignored. * Added a static check to fail builds on inert optional flag seams. * **Documentation** * Updated batch work-order and mega-batch branch guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- ## Late addition: the seam gate was masking a real offender `scripts/check-inert-flag-seams.mjs` matched call sites by NAME, so two same-named functions in different modules were conflated. I had documented that as a known false-positive source and moved on — reports mentioning `sortTasksForDisplayColumn` are noise, read past them. That annotation was the damage. Core's `sortTasksForDisplayColumn` genuinely never receives its `columnFlags` argument outside its own tests. The dashboard's separate function of the same name (`app/components/taskSorting.ts`), called with up to five arguments from `Lane`/`Board`/`ListView`, was raising the arg-count max and clearing core's seam. The offender was behind a row everyone had been told to skip. The gate now records the module each callee is imported from and matches it against the seam's declaring module. **Measured, by reverting the change:** the scan prints `17 seams, all supplied` and emits **no row** for the function. With the change, it is reported. Both directions watched. Reported on #2783 rather than fixed from outside — core owns it, and "wire the flags" vs "drop the parameter and let the literal stay counted" is their judgment call. TEMPORARY allow-list entry carries it meanwhile; the existing staleness check fails the moment the site becomes supplied, so the entry cannot outlive the fix. Two known limits remain, both inherent to name matching and both documented in the script: the one-supplier floor, and the `__tests__` exclusion (hence the two permanent `ALLOWED` entries). ## And the one-supplier floor, closed the same way I wrote in the section above that the floor "hasn't cost anything yet." That is verbatim the reasoning that kept the imported-shadow bug alive, so I closed it instead of leaving the note. `best < arity` asked only whether SOME caller supplied the argument. One correct call site cleared the seam while every sibling took the legacy fallback — the `isTaskStuck` defect class, where two of three sites omitted the flags and the gate stayed green because the third was right. Review caught that one. A partially-supplied seam is the harder of the two: wholly-unsupplied is uniformly wrong, this works on the board you tested and degrades on the column you did not. **Measured:** dropping the flags argument at `Column.tsx`'s supplied call site produces `supplied by 5/6 call sites; omitted at packages/dashboard/app/components/Column.tsx:1 (of 2)`; restoring returns `all supplied at every call site`. Red and green both watched. Two real omissions found, both on `isNearDuplicateCanonicalInactive`: - **`TaskDetailModal.tsx`** — deliberate, and it **corrects a note I left at that site**. The old note said hoisting the flags state was "the actual fix." It is not, for this call: the flags in scope describe the *modal's* task, and the canonical is a **different task** on a column this component never resolves. Passing them would type-check, read as a conversion, and answer about the wrong task — exactly what `column-role-degraded-flags.test.ts` exists to catch. Supplying it correctly needs a fetch, which is a data change and out of scope. - **`core/task-store/branch-group-ops.ts`** — genuinely wireable (the impl is async and already holds `store` and `canonicalId`). Reported on #2783, not edited from outside. Exemptions for this class are keyed by **call site** (`<file>::<function>`), not by function name. A name-level entry would waive every site of a partially-supplied seam, which is backwards — its other sites are correct and are the reason the omission is worth reporting. Both entries carry the same staleness check as the name-level list and cannot outlive their fix. Remaining known limit, now the only one: the `__tests__` exclusion, which makes a test-only export read as having no callers. That is what the two permanent `ALLOWED` entries are. ## The `__tests__` exclusion, and two allow-list entries built on false reasons Named as the "last remaining limit" above, so it got closed too. The scan now reads test files for call sites — but counts them **separately**, and a test never clears a seam. That direction is the dangerous one: counting test callers as suppliers would have re-hidden core's `sortTasksForDisplayColumn`, whose only suppliers are its own tests. Measured by lifting its exemption: still reported. Both permanent allow-list entries claimed the scanner couldn't see their callers. **Both reasons were false**, and reading tests is what proved it: - **`evaluateMergeBlockerGuard`** — zero callers in tests either. Its only reference in the repo is its own declaration; never registered as a trait hook; the `evaluateDefaultWorkflowGuards` reader its file header credits does not exist. The `lifecycleColumns` conversion went onto dead code, and its note describes a crossing the guard cannot make. Reported on #2783, including the two things I am explicitly *not* concluding (no `"guard"` hook is registered in production; whether that is residue or a dropped registration needs core's intent). - **`isRecoverableMissingWorktreeReviewFailure`** — 5 test call sites. It wraps `...WithProgress`/`...NoProgress`, the live pair called from `self-healing.ts`, both supplying `reviewColumns`. Entry kept, true reason recorded. ### A wrong turn, recorded because it is the failure mode this PR is about I first classified no-production-caller seams as *informational* when they weren't re-exported from a package index, reasoning that a public export might be called externally. That silently downgraded `sortTasksForDisplayColumn` — a confirmed real offender — from failing to a footnote. Publication status has nothing to do with whether there is production behaviour to be wrong. Reverted to the simple rule: no production caller means inert, and it fails. It is worth stating plainly because it is the exact shape of everything else in this PR: a change that made the gate read *cleaner* while making it catch *less*, and it type-checked, passed every test, and would have reviewed fine. ### Where that leaves the check Every blind spot named in this PR has now been closed, and **each one produced a real defect within minutes of closing it** — imported shadows, the one-supplier floor, the `__tests__` exclusion. Four verified findings went to core, one to engine. I would not read the remaining ~240 guards' green gates as evidence that they are clean; I would read them as untested. ## Two guards for one question, one of them worse Having hardened the script, I checked its older twin rather than assuming it was fine. `resolved-flags-seams-have-suppliers.test.ts` carried its own copy of the trailing-flags-parameter check — written before the script existed — with **all three** holes the script has since closed. **Measured on one reintroduced defect** (dropping the flags argument at `Column.tsx`'s supplied `isNearDuplicateCanonicalInactive` call): | | result | |---|---| | `scripts/check-inert-flag-seams.mjs` | `supplied by 5/6 call sites; omitted at .../Column.tsx:1 (of 2)` | | this test's arity half | **3 passed** | Deleted the arity half. Redundancy between a strong and a weak check isn't redundancy — it's a green result available to whoever runs the weak one, and there was no signal at the call site telling you which you were looking at. The **props-shape half stays**: it has no twin in the script, and I confirmed it still fires by reintroducing the original `PrPanel` defect (outer component stops destructuring `taskColumnFlags`) — it reports `PrPanel declares taskColumnFlags but never takes it`. Dashboard app suite: **113 files / 3921 tests** (was 3922 — the deleted case is the difference). ## The gate started catching defects as they landed Syncing with main brought in three fresh conversions from other workers. The hardened check flagged all three immediately — the first time these guards have fired on someone else's landed code rather than on my own. - **`TaskCard`** — `getRunningOptionalGateBadge(task)` omitted flags while *both* `ListView` sites supplied. Fixed, and `taskColumnFlags` added to the `useMemo` deps: no `exhaustive-deps` rule here, so a memo that reads flags without listing them keeps the first-paint `undefined` answer and reproduces the bug through staleness instead of omission. - **`TaskTokenStatsPanel`** — `getTotalAgentActiveMs` omitted while `TaskCard` supplied, so the same runtime number came from the real column on a card and from legacy ids in the detail modal. Now takes `columnFlags`, supplied from `detailColumnFlags` — correct here because the panel renders the modal's **own** task, unlike the near-duplicate canonical above. - **`ListView` ×2** — passed `columnFlagsById.get(task.column)`, the cross-workflow **union**. A task whose own workflow doesn't declare that column gets a *neighbour workflow's* traits. The landed comment justified it as "this list already owns `columnFlagsById`" — exactly the reasoning `column-role-degraded-flags.test.ts` exists to reject. It failed on merge and is how I found this. Also: the `getTotalAgentActiveMs` exemption I was carrying **self-retired**. Main wired the seam, the staleness check failed the entry, and I removed it. That mechanism has now paid for itself once. ### Pre-existing, NOT from this PR: `App.test.tsx` is red on main `app/components/__tests__/App.test.tsx` fails **10 of 141** identically with my changes, with my changes stashed, and with main's own `App.tsx` restored. Not mine, and not in the merge gate. **Bisected on clean `main` checkouts, so this is measured rather than inferred:** | commit | date | result | |---|---|---| | `main~400` (`41d60f0355`) | 2026-07-25 | **140 passed** (140 tests) | | `main~275` (`74d6513fae`) | 2026-07-27 | 3 failed / 141 | | `main~210` (`d2ce1ba8b5`) | 2026-07-29 | 10 failed / 141 | | `main` (`6fc98fd6c7`) | 2026-07-30 | 10 failed / 141 | So it is **not one regression** — it degraded in two stages across 2026-07-25 → 07-29, and the test file itself changed in that window (140 → 141 tests). Three commits touched it there: `73b2a32e2b`, `f26cbedf4f`, `f157bf7460`. That window overlaps the workflow-owned lifecycle migration, which is suggestive but not something I confirmed. The failures are render-level, not assertion-level — `Unable to find an element with the text: + New Task`, `Unable to find role="dialog"`, `Unable to find ... Back nav task`. The board appears to render nothing. That reads like a real regression or a harness mismatch after the lifecycle migration, not a flake, so I have deliberately **not** quarantined it — quarantine is for flakes, and using it here would hide the signal. Flagging for whoever owns `App.tsx`. My suites: `app/__tests__` **113 files / 3921 tests** green, `tsc` 0, lint 0, census `--strict` 0, seam gate 0. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
76d77da4d1 |
fleet: taskSorting + TaskReviewTab 8 → 0 — and Board was faking a column id to force done-sorting (#2744)
Two app-side clusters, 8 → **0**, plus a caller-side hack retired. ## What was broken **`TaskReviewTab.tsx`** — three of its four questions were `task.column === "in-review"`, driving the **Create-PR button**, the **"frozen on entry to review"** auto-merge hint, and **PR-feedback addressing**. On a renamed review lane all three took their non-review branch: the button was absent, and the hint claimed the effective auto-merge value was *not* frozen when it was. **`taskSorting.ts`** — `isReviewColumn` decides whether merging cards float to the top of a lane. Keyed on the id it silently stopped doing that on any renamed review lane, so the operator loses the "what is merging right now" ordering with nothing failing. Both follow the shape this code already established: **caller supplies the trait, default to the legacy id**. `columnFlags` on the review tab is optional and wired from `TaskDetailModal`, which already resolved it for `canEdit` and the actions menu. ## A synthetic column id, retired `Board.tsx` forced done-sorting by passing the **literal `"done"`** as the column argument for any complete-flagged lane: ```ts grouped[column.id] = isWorkflowDoneLikeColumn ? sortTasksForDisplayColumn(grouped[column.id] ?? [], "done", doneSortMode) : sortTasksForDisplayColumn(grouped[column.id] ?? [], column.id as ColumnType, ...); ``` A synthetic id standing in for a trait — so a custom complete lane sorted correctly only because its caller **lied about its name**. Both call sites now pass the real column id and state the trait. (Board's own census count stays at 2: those two literals were the synthetic ids and are gone; the 2 remaining are different sites.) ## Revert proof | reverted | failure | |---|---| | `task.column === "in-review"` on the Create-PR guard | `Unable to find an element by: [data-testid="task-review-create-pr"]` | | same, on the auto-merge hint | `expected 'Effective: Auto-merge off' to contain 'frozen on entry to review'` | A third case pins that the widened test does not treat *every* column as review. **None of the 45 existing `TaskReviewTab` cases could have caught this** — `columnFlags` is optional and they all omit it, so they assert the legacy fallback. That is the same blind spot as the reconciler's 33 in #2737, and it keeps recurring: an optional-flags seam means the existing suite stays green through the conversion *and* through a broken one. ## A process failure worth recording **I lost this conversion once and had to redo it.** I overwrote four files with their `origin/main` versions to check whether a failing test was pre-existing, then "restored" with `git checkout HEAD -- <dir>`. HEAD was still `origin/main` because I had not committed, so that **discarded the work**. Same class as the shared-stash incident two PRs back: an implicit or positional restore reference. The fix is ordering, not care — **commit before any baseline comparison**, so `git checkout HEAD -- <file>` restores my work rather than main's. This PR's commit was created before the comparison for exactly that reason, and the note is in the commit message so the next person hits it there too. ## Verification `pnpm test:gate` **GREEN** (158 + 10 + 487 + 71) · **232 passed** across TaskReviewTab / taskSorting / Board suites · dashboard `tsc -p tsconfig.app.json` clean · `pnpm lint` clean · census `--strict` exits 0. The 1 `board-mobile` failure is **pre-existing** — verified by swapping in clean `origin/main` copies of all four files and reproducing it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
28c069ac0c |
fix(dashboard): TaskDetailModal — one root cause that produced six separate review findings (#2761)
## The pattern `workflowMoveMetadata` outlives a task switch, so while the modal is open its flags describe the **previous** task for a render. This component gates editability, the execution-mode replan decision, the intake affordance, the actions menu and the review tab on them. **The guard already existed** at line 961: ```ts const detailFlagsAreForThisTask = workflowMoveMetadata?.taskId === task.id; const detailColumnFlags = detailFlagsAreForThisTask ? workflowMoveMetadata?.currentColumnFlags : undefined; ``` Five call sites read **around** it. Each was found separately — #2744 (review tab), #2696 (`handleDelete` deps), and the four converted here: | line | consumer | |---|---| | 1827 | `canEdit` | | 2147 | execution-mode replan on save | | 2285 | execution-mode replan on mode change | | 3156 | `isIntakeColumn` | | 3782 | actions-menu model | **Six review rounds for one root cause.** Converting them together retires the class instead of paying a round per site — the same arithmetic as the review-lane family, which took #2730 and #2750 to end rather than eight per-file patches. Only the definition line still reads the raw value, which is the point of it. ## Not covered by a new test — stated rather than papered over is internal state populated by a fetch, not a prop, so the stale-flags scenario needs the workflow-metadata request mocked **plus** a task switch mid-render. That is a real test worth writing. It is not a line I can add honestly in passing, and I have shipped four tests today that passed with the bug fully in place — I would rather flag the gap than repeat that. This file's suite also carries **6 pre-existing failures**, confirmed identical on clean by stashing and re-running, which would muddy the signal from a new case. ## What is verified - the narrowing is mechanical and **total** — one remaining raw read, the definition; - pre-existing failure count **unchanged at 6/45** with and without this change; - 106 passed + 5 skipped across both `TaskDetailModal` suites; - dashboard `tsc` clean (`tsconfig.app.json`), lint clean (0 errors), gate green (487 + 158 + 10 + 71). No census movement — this changes which value is read, not whether a column id is compared. |
||
|
|
8e50967279 |
fix: restore current main regression invariants (#2755)
## Summary - align the renamed-review CLI regression with the classifier’s resolved-boolean contract - keep GitHub tracking controls expanded across same-task detail and sparse SSE updates - strengthen the sticky tracking regression to wait for the sparse update ## Test plan - `pnpm --filter @runfusion/fusion exec vitest run src/__tests__/cli-active-count-lanes.test.ts` - `pnpm --filter @fusion/engine exec vitest run src/__tests__/restart-recovery-coordinator.test.ts` - `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx` - `pnpm --filter @fusion/dashboard typecheck` - `pnpm --filter @runfusion/fusion typecheck` - `pnpm check:changesets` |
||
|
|
2a293ee1e0 |
fleet: TaskDetailModal.tsx 30 -> 7 (#2698)
## Census before / after | | before | after | |---|---:|---:| | `TaskDetailModal.tsx` | **30** | **7** | | repo backlog | 721 | **698** | Backlog dropped by **23** — the converted count, nothing else moved. **Not 30 → 0.** The seven survivors are enumerated below with reasons rather than absorbed into the number. ## Converted (23) Four role bindings declared immediately after `workflowMoveMetadata` — their source — and 20 in-component comparisons collapsed onto them. Two module-scope helpers (`resolveDefaultTab`, `requiresExecutionModeReplan`) take a bare column id with no flags in scope, so they use the fallback-only form. That is **centralisation, not trait resolution**, and each is labelled as such at the site so it stays greppable as "still needs its flags threaded". ## Not converted (7), each with a reason | count | site | why | |---:|---|---| | 2 | `showNearDuplicateWarning` (~881) | Sits **above** `workflowMoveMetadata`, so referencing the role bindings is a temporal-dead-zone error. Needs the state declaration hoisted — behaviour-safe, but it reorders hooks in a 6500-line component, which is not a vocabulary edit. | | 2 | two `useEffect` dep arrays (~930, ~936) | Same problem, subtler: the callback *bodies* could reference the bindings, but a **dep array is evaluated eagerly** at the hook call, which is above the declaration. Same hoist. | | 2 | `overlapBlockerTask.column` (~3559) | A **different task's** column. The modal holds flags for its own card only; resolving the blocker's role means fetching its flags. Out of scope. | | 1 | `session.agentState === "done"` (~353) | An **agent state, not a column**. Already reclassified in #2692 — this file drops to 6 counted when that lands. | ## Late-arriving flags — checked, not assumed `currentColumnFlags` is `null` until the workflow fetch resolves, so every role here flips after first paint. That is the hazard that produced four stale memos in `TaskCard` (#2688 review), so I ran an AST pass over every `useMemo` / `useEffect` / `useCallback` in the file. **None needed a new dependency** — the 20 converted sites are all render-path expressions rather than memoised closures. Worth stating explicitly, because "no dep changes" in a conversion PR usually means nobody looked. ## Verification `pnpm test:gate` green (10 / 158 / 487 / 71). `pnpm check:lifecycle-columns` exits 0 with the baseline re-recorded here. `tsc -p tsconfig.app.json` clean. `pnpm lint` clean. Targeted `TaskDetailModal` suites pass (5/5). Note: the full `TaskDetail*` glob exceeds a 10-minute run locally, so I verified with the targeted suites plus typecheck rather than reporting a number I did not measure. No changeset: no user-visible change. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
dc50425e98 |
docs: correct 104 future-dated FNXC timestamps across 61 files (#2680)
## What The FNXC convention exists so a reader can place a note against the change that motivated it. A stamp dated *after* the edit landed defeats exactly that. This is program-wide drift, not one author's slip — I contributed to it in my own commits this week, which is how I noticed it. ## Measured, on this tree **104 stamps across 61 files** dated later than the day they were written, from one day ahead to **2026-10-19 (81 days)**: | count | date | count | date | count | date | |---|---|---|---|---|---| | 50 | 2026-07-31 | 6 | 2026-08-05 | 3 | 2026-08-13 | | 17 | 2026-08-01 | 1 | 2026-08-07 | 1 | 2026-08-19 | | 7 | 2026-08-02 | 1 | 2026-08-12 | 2 | 2026-08-26 | | 11 | 2026-08-03 | | | 3 | 2026-10-19 | An earlier number I circulated was ~70. That came from a narrower pathspec and was wrong; **104** is the measurement. ## How Each stamp is rewritten to the date of the commit that introduced **that line**, via per-line `git blame` — deliberately *not* stamped uniformly with today's date. A uniform stamp swaps a wrong date for a different wrong date and flattens the ordering that makes these comments navigable; blame preserves it. Times of day are untouched, and a blame date in the future is clamped rather than trusted. ## Why the verification is listed A docs sweep across 61 files is precisely where a stray edit hides, so the safety claims are mechanical rather than asserted: - every changed line begins with a comment marker — **no code touched**; - **no test asserts an FNXC date later than today**, so no `toContain` assertion on embedded source text can be silently invalidated (several such assertions do exist); - CSS files, which carry several of those assertions, are outside the pathspec. ## Verified lint clean · merge gate green (487 + 158 + 10 + 71) · `census --strict` exit 0 · tsc clean for core, engine, and dashboard (`tsconfig.app.json`). **No behavior change.** Comment text only. ## Not done here A guard preventing recurrence. A check that rejects an FNXC stamp dated after the commit would stop this returning, but it needs a decision about where it runs (lint rule vs. gate) and it is a behavior change to CI — it does not belong riding inside the sweep it would police. |
||
|
|
2771408bba |
ci: enforce the lifecycle-column ratchet — it has never actually run (#2654)
**The ratchet was advisory.** `scripts/lifecycle-column-census.mjs` existed only as `pnpm census:lifecycle-columns` — without `--strict` — and **no workflow invoked it**. Nothing has ever compared the tree to the baseline. Every "the baseline ratchet holds them" assumption in this program rested on a check that does not run. That explains both classes of hole: **1. Three PRs lowered counts without re-recording,** leaving allowances the deleted guards could return through while every check stayed green. I've tightened them across #2593 and earlier PRs, but nothing stops the next one. **2. #2621 GREW the count while its own title claimed "count 0 → 0".** It added `column === "triage"` and `column === "todo"` at `register-task-workflow-routes.ts:2681`, taking that file to **23 against an allowance of 22**. It landed unchallenged. This is the failure mode the ratchet exists to prevent, and it happened *inside this program*, in a PR that asserted the opposite. ## The change Adds `check:lifecycle-columns` (the census with `--strict`) to the `pr-checks.yml` lint job, next to `check:changesets` and `check:routes-modular` — the established pattern. **~1.8s over ~1950 files**, so this is not a slow-test addition. ## Proven to fail, in both directions A guard that reports success without checking anything is worse than no guard, so: | injected defect | result | |---|---| | `const __probe = (c: string) => c === "triage"` added to `moves.ts` | `count ROSE — moves.ts: 39 -> 40`, exit 1 | | run against main's current baseline | exit 1 on `mission-feature-sync.ts: allows 5, tree has 0` | Both reverted; exit 0 restored. Note the second row: **this check is RED on main right now**, which is the point. ## Merge order **Stacked on #2593**, which carries the `DELIBERATE-LITERAL` marker for the #2621 site (a v1 IR declares no roles, so no trait can answer that question) plus the baseline re-record. Standalone on main this PR is red — correctly. **Merge #2593 first**, then this. I stacked rather than duplicating those two edits because I already caused one conflict today by appending related content from two branches, and #2651 merged a correction ahead of the section it corrected. Same-content edits in two PRs is the same mistake. ## Census Unchanged by this PR: **776 total, triage 5, reviewed 16** — it adds no guards and converts none. It only makes the numbers enforceable. ## For the fleet This should land before the 776-guard fleet launches. The brief says "the baseline ratchet must shrink by exactly the converted count" — until now nothing verified that claim, so a batch worker could report a shrink that did not happen, or grow the count while converting, and CI would agree. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
642a4fa264 |
consolidate/u12 — U12 consolidation: 4 live defects, the AST ratchet fail-closed, and the moves.ts flag scoped (#2647)
One branch, one PR, per the consolidation directive. Contents file-by-file below. **Supersedes #2625** (its overlapping conversions landed via U11's #2624/#2626/#2636; only the parts nobody else did are folded here). **#2630 and #2639 stay open** — both green with zero threads, per rule 3. ## Four live defects, each measured **1. Every planning card renders an actions menu.** `TaskContextMenu.tsx` still had `shouldShowActionsMenu: task.column !== "triage"` on main *after* the rest of that file was converted. Since #2515 removed the id, the condition is TRUE for every card, so the suppression stopped applying anywhere — including on cards whose menu is empty, the orphaned click target the Surface Enumeration rule exists to catch. Found **twice independently**: by reading the guard, and again by the invariance test below, which failed on main with `shouldShowActionsMenu` true on one lineage and false on another. That is the argument for an invariance property over per-site conversion — the file had already been converted "2 → 1" and the survivor was the live one. **2. Worktree upcoming-work list empty on renamed boards.** `groupByWorktree` filtered `t.column === "todo"`. On the default board the id and the role coincide so every existing test passed; renamed, it matched nothing and a whole panel read as idle. **3. Hold-lane FIFO ordering lost on renamed boards.** `sortTasksForDisplayColumn` gated priority-then-FIFO on `column === "todo"`, degrading to the generic id-ordered sort elsewhere. Cards simply appear in the wrong order, silently. **4. The AST ratchet still failed open** — fourth time in that file, third found by review. `receiverName` understood only one-level property access and bare identifiers, so `task["column"]`, `metadataColumn(entry, "to")`, ternaries, `(task!.column)` and backtick literals were dropped. **Measured on main: `in-progress` 196 → 197, `in-review` 211 → 213** — three real guards nobody counted, including `metadataColumn(entry, "to") === "in-review"` in `reliability-metrics.ts`. Now walks wrappers, resolves calls to the callee name, and emits a `<SyntaxKind>` **sentinel** for anything unnameable: counted *and* trips the classification guard, so a human judges it instead of it vanishing. ## Per-file guard counts | file | before | after | |---|---:|---:| | `app/components/TaskContextMenu.tsx` | 1 | **0** | | `app/utils/worktreeGrouping.ts` | 1 | **0** | | `app/components/taskSorting.ts` | 1 | **0** | The other dashboard files I had converted reached 0 via U11's PRs; where our work overlapped I took theirs during the rebase, including two places where theirs was **stronger** than mine — they deleted Column's unreachable quick-create arm outright (with fixtures migrated) where I had converted it, and they verified the same `isPreExecutionHoldColumn` degraded-set asymmetry I did, independently. ## Flip precondition: the moves.ts flag is scoped, not flipped `move-target-declared-census.test.ts` answers precondition 2 with measurement. 41 engine `moveTask` calls have literal targets — `todo` 27, `in-progress` 7, `done` 6, `archived` 1 — and **all four are declared by the default lineage**, so the default board is not the exposure. `triage` appears only in a comment noting `replan-target.ts` used to hardcode it. My own grep had said `todo=29`; the AST says 27, because grep counts comments. The exposure is **custom** lineages: 20 of the 41 carry no `recoveryRehome` and would reject with unknown-column post-flip; 21 are exempt via the #1411 carve-out, which makes that carve-out load-bearing. I did not flip the flag. It is six seams, not the `789`/`837` pair every summary including mine described, and seam 2 turns on *new refusals* rather than swapping equivalent implementations — a green suite says nothing about that. #2639 pins the blast radius. ## Tests - `column-role-id-invariance.test.tsx` — hold traits fixed, vary only the column id across MERGED / LEGACY / RENAMED; every decision must agree. Drives the real consumers, so a component keeping an inline comparison fails it. Includes a unanimous-and-**false** case so it can't be satisfied by a predicate hardwired to true. **This is the test that caught defect 1 on main.** - `worktreeGrouping.test.ts` — includes two cards both in a column named `staging`, one hold and one not, asserting opposite answers. That assertion is impossible under a board-wide column-id set, which is why hold resolution is keyed per task via `getEffectiveTaskWorkflowId` (#2625 review). - `taskSorting.test.ts` — discriminates on the **tiebreak**, not priority: both branches sort by priority, so my first version passed for the wrong reason. Equal-priority cards whose `createdAt` order disagrees with their id order. - `no-hardcoded-lifecycle-columns.test.ts` — 16 detector cases: 11 shapes counted, 4 legitimate ignored, one asserting the sentinel path. Revert checks, all run: menu suppression → diff names the field; worktree → `expected [] to include 'FN-50'`; sort → `FN-2, FN-9` instead of `FN-9, FN-2`; ratchet → the 3 recovered guards disappear. ## One site that should never be converted `MissionControlPanel.tsx:46` — `{ id: "triage", match: (c) => c === "triage" || c === "signal" || c === "backlog" }` is a deliberate name-similarity heuristic for the SDLC funnel; it matches synonyms and folds unknown columns into an "other" bucket so custom columns still contribute. Converting it changes what the funnel displays. Like the `live-agent-count` fallbacks, it belongs in a documented floor — **the ratchet's target is that floor, not zero.** `DocumentsView.tsx:73` is convertible but the file has no column flags at all, so a real fix means plumbing board-workflow metadata into a view that doesn't fetch it — its own unit of work. ## Verification `pnpm lint` clean. `pnpm test:gate` green (10 / 482 / 71). `tsc -p packages/dashboard/tsconfig.app.json` and `packages/core/tsconfig.json` clean. Core ratchet + seam suites 24/24. Dashboard target suites 37/38 — the one failure is the pre-existing `"Back to In Progress"` label casing, confirmed identical on the base. --- ## Added after the initial push **5. `TaskCard` lost inline editing on renamed boards; `TaskDetailModal` kept it.** Still live on main: the modal resolved field editability from traits in U10/R8, the card used a hardcoded `{triage, todo}` set with **no trait path at all** — even though `taskColumnFlags` was already in scope. On a renamed board the title was editable in the modal and the pencil was missing from the card. Body moved unchanged into `isFieldEditableColumnRole` so the two surfaces cannot drift again. The veto traits are the substance: a column can legally carry `hold` **and** a WIP or review trait, and a plain `intake || hold` check would let an operator rewrite a description while a session executes against it. Coverage gap **measured, not assumed**: mutating `canEdit` back to the hardcoded set left `TaskCard*` at the same failure count as the unmutated run — nothing caught it. The four render cases assert the real `aria-label`; that mutation now fails with `Unable to find an accessible element ... name 'Edit task'`. **6. The ratchet's target is a documented FLOOR, not zero** — and this changes the completion bar. Zero is not reachable, and chasing it means breaking working code. Two categories are permanent, now protected as positive assertions so a future sweep cannot "finish the job" by deleting them: - `MissionControlPanel.tsx`'s `FUNNEL_STAGES` is a deliberate **name-similarity** heuristic — it matches `signal`, `backlog`, `to-do`, `ready`, `shipped` and folds unrecognised columns into an "other" bucket so a custom board still contributes counts. It is not asking whether a column has the intake trait; it buckets arbitrary column *names* for display. Asserted on the **synonym list**, because the synonyms are what prove it is name matching — if they disappear the site has changed character and the exemption stops applying. - `live-agent-count.ts`'s no-flags arm is reachable (a remote store is deliberately given an empty flag map; a card in an undeclared column has no flags at all) and deleting the literal makes such a card match **no** arm, so the queued total silently under-reports a stranded card. A count with an undocumented floor invites someone to drive it to zero. **Not done, and why:** `DocumentsView.tsx:73` is convertible but that file has no column flags anywhere, so a real fix means plumbing board-workflow metadata into a view that does not fetch it — its own unit of work, not something to smuggle into a conversion. **Re-verified after these commits:** `pnpm lint` clean, `pnpm test:gate` green (10 / 482 / 71), `tsc` clean on core and `tsconfig.app.json`, core ratchet suite 26/26, `columnRoles` 10/10, `TaskCard.test.tsx` 384/386 (the 2 are pre-existing CSS assertions). `TaskDetail*` is 130 failed / 551 passed **both with and without** this change — verified by stashing, so pre-existing and unrelated. --- ## Flag resolution: preconditions 1 and 2 are now DISCHARGED. Precondition 3 is blocked, and by evidence. **Precondition 1 — the side-effect equivalence proof — done.** `moves-flag-equivalence.test.ts` runs the same journey under both flag states against live PG and diffs the persisted row. **Result: identical** — whole-row equality across 128 fields plus an equal timing shape, over `todo → in-progress → in-review → todo → in-progress`. That test was **wrong twice** before it meant anything, and both times it was passing: 1. **It proved nothing.** `experimentalFeatures` is **global-only**, and `moves.ts` reads `getSettingsFast()`, which filters global-only keys out of the project layer. My `updateSettings` write was silently discarded, `useWorkflow` was false in *both* runs, and the "proof" compared the legacy path against itself. Found by stamping the flag-ON branch and observing the test still passed. Now written via `updateGlobalSettings`, and the helper **asserts the flag took effect** before the journey runs. 2. **The journey was forward-only**, so it never reached the reopen hook's field resets (`status`, `error`, `blockedBy`, pause clearing) — a mutation there passed. Extended with a backward move and a re-entry. Mutation-verified after both fixes: stamping seam 3, and diverging the reopen hook, each fail the comparison. **Precondition 2 — done, and its answer is a blocker.** The census says the default board is safe: all 41 literal engine move targets are declared by the default lineage. But **20 of those 41 carry no `recoveryRehome`**, so on a custom lineage that does not declare `todo` / `in-progress` / `done`, seam 2 would start rejecting them with unknown-column. That is a user-facing break on custom boards, not a theoretical one, and it is not fixed by the equivalence proof — seam 2 adds *new refusals* rather than swapping implementations. **So the flip is one step away, and the step is not mine to take alone:** those 20 call sites need to resolve their target from the task's workflow (or justify `recoveryRehome`), and they live across engine lanes in `moves.ts` caller territory — U2b/MAIN. Flipping before that trades a dormant flag for broken custom boards. What remains for precondition 3 once those land: flip both readers **atomically** (`moves.ts` + `workflow-task-create-ops.ts`, since the latter computes the preflight the former consumes), delete the flag-OFF branch with its guards, and drop the settings key. --- ## CORRECTION: seam 2 is not a blocker. My earlier claim was wrong. I stated in #2639 and above that "with the flag off there is **no** target-column validation on the move path", so flipping would introduce new refusals. **That is not what happens.** Reproduced against live PG: the identical custom-lineage move rejects with the flag **OFF** as well — ``` Error: Invalid transition: 'backlog' -> 'todo'. Valid targets: building ``` Transition validation is already in force on the flag-OFF path. So for the shape in question — an engine move to a column the task's own workflow does not declare — **the move already fails today**, and seam 2 introduces no new break for it. The 20 census sites lacking `recoveryRehome` are broken on a custom lineage *now*, not broken by the flip. I found this because the discriminator I added to prove "the flag is the cause" failed. Had I written the test to my assumption it would have passed and the false claim would have shipped — the same way the equivalence test passed while proving nothing until I tried to make it fail. **Revised precondition status:** | precondition | status | |---|---| | 1 — side-effect equivalence | **discharged** — identical rows, mutation-verified both directions | | 2 — seam-2 exposure census | **discharged, and it is not a blocker** — the rejection predates the flag | | 3 — flip both readers atomically, delete the flag-OFF branch, drop the settings key | **the remaining work** | So the flip is no longer gated on fixing 20 engine call sites. What it is still gated on is precondition 3 being done atomically across `moves.ts` and `workflow-task-create-ops.ts` (the latter computes the preflight the former consumes), which is `moves.ts` caller territory. Three cases now cover seam 2: the flag-ON rejection, the flag-OFF rejection (asserting the error *message*, so a change in which guard rejects stays visible rather than reading as agreement), and the #1411 `recoveryRehome` carve-out succeeding — pinning why that carve-out is load-bearing and must not be tidied away. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bad35775a1 |
Drift 3/4: Task Detail intake affordances from traits — the UI half of the #2571 approve/reject stall (4→3) (#2577)
## Drift conversion 3 of 4 — Task Detail, the UI half of the #2571 stall **Stacks on #2566.** Merge order: #2558 → #2566 → this. (#2571 is the P0 and is independent — merge it first regardless.) ### Convergence number Live-code `column === / !== "todo" | "triage"` in `TaskDetailModal.tsx`: **4 → 3** All three survivors are the documented no-metadata fallback, same shape as TaskCard and ListView: `workflowMoveMetadata` is `null` until the detail payload resolves, and a bare trait read would drop these controls during that window. ### This is the UI half of the P0 `isAwaitingApproval` and the standalone Delete button were both gated on `task.column === "triage"`. On the merged lineage (#2515) that is false for every card, so a task parked `awaiting-approval` **loses its Approve/Reject controls in the one surface that shows them**. #2571 fixes the routes that *reject* those actions. This fixes the UI that stops *offering* them. Either half alone leaves the operator stuck — one with buttons that 400, the other with no buttons at all. ### Three conversions | site | was | now | |---|---|---| | `isAwaitingApproval` + standalone Delete | `column === "triage"` | resolved column's `intake` | | `requiresExecutionModeReplan` | `todo \|\| in-progress` | `hold \|\| countsTowardWip` | | move-progress prompt | source column ids | **target** column's flags | The replan rule is "this card may already hold a plan or a live execution context" — which the traits state directly; `todo`/`in-progress` was the Default workflow's spelling of it. The move prompt is the mistake I made first in TaskCard, where its regression test caught that the site tests the move **destination**, not the card. Carried the lesson here rather than repeating it. ### Tested through a pure seam, and why `requiresExecutionModeReplanForTest` is exported so the rule can be asserted as a function of (column id, flags). Asserting it through the modal means booting async detail loading to observe one boolean — and an earlier DOM-level attempt at exactly this class of assertion (in #2566, ListView) **passed with the conversion reverted**, because the text it matched also appears in a column header. I am not repeating that. A seam discriminates; that DOM test did not. Revert-proof: restore `column === "todo" || column === "in-progress"` and the merged-column case fails, because that column is `intake + hold` and carries no `countsTowardWip`. The suite also pins that the rule still **narrows** (a complete lane needs no replan) and that the legacy fallback is unchanged when flags are absent. ### Verification `pnpm test:gate` (414 + 10 + 71), `pnpm lint`, dashboard typecheck green. **No new failures**: `TaskDetailModal.rendering.test.tsx` reports the same 28 pre-existing failures with and without this change, diffed by test *name* against a stashed clean tree. ### Drift set status | file | before | after | PR | |---|---|---|---| | `TaskCard.tsx` | 8 | 3 | #2558 | | `ListView.tsx` | 5 | 3 | #2566 | | `taskActivity.ts` (found underneath) | 1 | 1 | #2566 | | `TaskDetailModal.tsx` | 4 | 3 | this | | `register-task-workflow-routes.ts` | 10 | 11 | #2571 (P0, widened on purpose) | Survivors are no-metadata fallbacks except the routes, where the guards deliberately accept resolved-intake **or** `triage` so a P0 fix cannot reject anything previously allowed. Those retire together once the legacy id is gone board-wide. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
da0351857e |
U12 part 5: put real workflow adjacency on the wire — custom-workflow move menus were guessing (measured), and the VALID_TRANSITIONS shortcut is gone (#2525)
## U12 part 5 — the move menu was guessing; now it asks the graph **Stacks on #2521** (same file). Merge that first. The context menu had **no adjacency data at all**, so it did two wrong things at once: it approximated move targets from a column's **neighbours in declared order**, and — because that approximation is strictly weaker than the real graph — it kept a `VALID_TRANSITIONS` shortcut for any workflow whose column-id set matched the six built-ins. Measured, the approximation loses real operator moves: | current | workflow graph | neighbour approximation | |---|---|---| | `in-progress` | in-review, todo, triage, done | todo, in-review | | `todo` | in-progress, triage, archived | triage, in-progress | | `done` | todo, triage, archived | in-review, archived | So **every custom workflow has been offering a guess**: menu entries the store would reject, and legal moves it never offered. The built-ins were fine only because the shortcut bypassed the guess entirely. ### The fix `BoardWorkflowColumn` gains `moveTargets`, resolved by `resolveAllowedColumns` — *the same resolver `moveTaskInternal` validates against*. The menu now offers exactly what the store will accept, for any workflow. Threaded through all four metadata builders (Board, Lane, ListView, TaskDetailModal). Optional on the wire, deliberately: a client older than this field keeps the neighbour fallback rather than losing its move menu mid-upgrade. ### Why deleting the legacy shortcut is safe Not an assertion — a measurement, then a pin. `resolveAllowedColumns(BUILTIN_CODING_WORKFLOW_IR, c)` is **identical to `VALID_TRANSITIONS[c]` for all six columns, order included**: ``` triage ["todo","archived"] == VALID SAME todo ["in-progress","triage","archived"] == VALID SAME in-progress ["in-review","todo","triage","done"] == VALID SAME in-review ["done","in-progress","todo","triage"] == VALID SAME done ["todo","triage","archived"] == VALID SAME archived ["done"] == VALID SAME ``` `builtin-adjacency-matches-legacy-transitions.test.ts` pins it so the equivalence cannot drift silently — if the built-in workflow's edges change without `VALID_TRANSITIONS` following, default menus change shape and that test fails first. It compares **order** too, since the menu renders targets in the order it receives them, so a reorder is operator-visible. Default-workflow menus are therefore byte-identical. Custom ones stop guessing. ### What's left of the legacy vocabulary here `COLUMNS` is gone from `TaskContextMenu` — deleting the shortcut removed its last use. `VALID_TRANSITIONS` survives for exactly one thing: the **no-metadata load window**, documented at the site. I measured removing that in #2521 and it left Task Detail with no move options during load, which is a regression rather than a cleanup. It retires when the load window does. ### Revert-proof, two ways - Drop the `declaredTargets` branch → the custom-workflow case fails: the neighbour fallback returns `["backlog","building"]`, missing the legal `shipped` jump **and** offering `backlog`, which that graph forbids. That is exactly the defect class shipped to every custom workflow today. - A second case pins that an adjacency edge into a column the board cannot show is **dropped**, not rendered as a dead menu entry. ### Verification `pnpm test:gate` (309 + 10 + 71), `pnpm lint`, `pnpm verify:fast`, core + dashboard typechecks green. **No new test failures**: five suites report 31 failures with and without the change — an identical, pre-existing set, verified by diffing failing test *names* against a stashed clean tree, not by comparing counts. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Move menus for custom workflows now show only the destinations permitted by that workflow. * Task-specific workflow rules are applied consistently across boards, lists, lanes, and task details. * Invalid or unavailable destinations are excluded from move options. * Existing clients remain supported when workflow destination data is unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fd6d005333 |
U12 part 1: delete the legacy board path (262 ListView + 39 Board tests were measuring it; 9-site flag inventory, moves.ts group blocked on U2b) (#2500)
## U12, part 1 of 2 — and one blocker you need to route The unit's headline deletion (`isWorkflowColumnsCompatibilityFlagEnabled`) is **blocked by U2b** and is not in this PR. What is here is everything that could be deleted without making a convergence decision that belongs to another unit. ### The blocker PR #2468 landed as `b941d3cba` — but that was **Phase A2 steps 1–2 only: the differential characterization**. The convergence (pick a path, delete the other, delete the flag) has not landed; `feature/workflow-move-path-convergence` is still live. Deleting the raw flag **is** that convergence. `move-path-equivalence.pg.test.ts` says so in its own header, and its second `describe` is literally *"the flag gates MORE than side effects"*. The plan makes this a blocking unit with an equivalence *proof obligation* and an explicit "stop and escalate rather than reconcile silently" note. So I stopped. ### Inventory: every read of the raw flag, with a verdict Nine sites. All false in production because nothing writes `experimentalFeatures.workflowColumns`. **Blocked on U2b — one branch, not separable:** | Site | Silently disabled today | Visible if flipped | |---|---|---| | `moves.ts:312` `useWorkflow` | typed `TransitionRejectionError`, workflow adjacency, the shared transition invariants (merge-blocker *trait* generalization), plugin column gates, the `transitionPending` marker, `workflowId` in `task:move` run-audit, and the trait-hook side-effect path | Yes — rejections change **type and message** | | `moves.ts:931` | the in-transaction capacity gate. `resolveColumnCapacity` never runs | Yes — WIP limits begin binding | | `workflow-task-create-ops.ts:351` | `prepareWorkflowMovePolicyPreflight` returns `undefined` unconditionally → **workflow/plugin move policies have never been evaluated** | Yes — new rejections | On #2488: the pool-id sentinel fix is correct *and* still inert. Two dead layers stacked — the gate it fixed is inside `if (useWorkflow && …)`. **Not blocked, but each moves operators' cards — deferred to PR 2 per your call:** | Site | Silently disabled today | |---|---| | `workflow-ops.ts:183` | `OccupiedColumnsError` + `rehomeTo` when a workflow edit removes an **occupied** column. Today the save succeeds and strands the cards | | `workflow-ops.ts:344` | occupant re-home on workflow **delete** | | `workflow-definitions.ts:700` | workflow-**switch** reconciliation, and the `reconciliation` field in the API response | I verified these three are **not** coupled to `moves.ts`: `rehomeOccupant` reaches a custom target via the `isWorkflowDeclaredRecoveryRehome` carve-out (`moves.ts:641`), which exists because the repair "silently no-oped on every store open" before it. **Not blocked, no behaviour change for current binaries** (also PR 2): `project-store-ops.ts:687` + `lifecycle-ops.ts:1119` — `downgradeIrToV1IfPure` on persist, for *binary-downgrade* rollback. Needs a round-trip test, not an assumption. ### What this PR deletes **Dashboard.** `workflowColumnsEnabled` was a literal `true` at all three `MainContent` call sites; the server hardcodes `flagEnabled: true`. Gone: Board's legacy single-lane board (55 lines mapping the hardcoded `COLUMNS` enum — the last board surface deriving columns from the legacy vocabulary, an R8 violation that survived U10); `tasksByColumn` and its cache ref, orphaned with it; ListView's `LEGACY_LIST_COLUMNS` (the ListView copy of the synthesized-trait-flags defect U10 fixed in Board); both props; the `shouldHydrateCache` gate; TaskDetailModal's `flagEnabled` early return. **Neither Board nor ListView imports the legacy column enum any more.** **Core.** `evacuateCustomColumnsToLegacy` (#1409) — both triggers require the previous settings to have the flag ON, which no writer produces. `runWorkflowColumnsIntegrityPass` — no caller anywhere, superseded by `reconcileUndeclaredTaskColumns` (registered in startup recovery), and it read through the sync SQLite handle, so invoking it under PostgreSQL would have thrown rather than reconciled. **Migration answer:** a project with `workflowColumns: false` persisted needs no migration and no read-time drop. Nothing in this PR reads the key, and it stays in `HIDDEN_EXPERIMENTAL_FEATURE_KEYS` so Settings still suppresses it rather than resurrecting it as an unknown setting. Proven by tests, no instance booted. **`flagEnabled` stays on the wire** as a constant. Removing it changes the response shape, and a browser tab outliving a server upgrade would read the missing field as "off" and degrade. One boolean, no client branches on it, droppable a release later. ### Measured - Production sources: **-332 / +131** (net **-201**). Additions are almost entirely FNXC comments recording why each branch was unreachable. - Dashboard production only: -168 / +93. - Core: -164 / +38. ### The finding I'd actually flag `Board.test.tsx` and `ListView.test.tsx` both left `workflowColumnsEnabled` unset and stubbed `fetchBoardWorkflows` with a **never-resolving promise**. Under the old gate that rendered the **legacy** board — so **262 ListView tests and 39 Board tests were asserting against a configuration production never reached**, and a real regression in the workflow board or list would not have failed either file. Same shape as the other four: looked enforced, wasn't. Both now seed the first-paint lane cache with the default workflow's **real** columns (ids and names copied from `BUILTIN_CODING_WORKFLOW_IR`) — the same seam production uses. Repointing them surfaced assertions that encoded legacy-only values: `"In Progress"`/`"In Review"` (real IR names are `"In progress"`/`"In review"`), and Planning Mode asserted to receive `null` as the workflow id, which is only what `getTaskPlanningWorkflowId` returns when `workflowMode` is false. `"Back to In Progress"` is **not** one of those — it is a hardcoded i18n string in `TaskContextMenu:210`, not derived from the column name. Left alone, and flagged: it will not follow a renamed column. That's U11 vocabulary territory. **One test is SKIPPED, not weakened** — "keeps unaffected columns stable when archived collapse toggles". Pointed at the real board the invariant is **false**: toggling the archived column re-renders unaffected columns (measured: todo renders 3×, not 2×). Pre-existing production behaviour this deletion exposed, never covered because the test measured the dead path. I ruled out the obvious causes (every callback prop is `useCallback`; the per-column task memo's deps exclude `archivedCollapsed`; memoizing the inline `canDropTask` binding did **not** close it — I wrote that fix, could not prove it with a failing test, and **reverted it**). The reason is recorded at the test: un-skip with a fix, never with a new expected number. ### Verification `pnpm test:gate` (299 + 10 + 71), `pnpm lint`, `pnpm verify:fast` (17 steps), and both package typechecks green. `settings-defaults.test.ts > warns once per process for legacy cwd-main mode` fails — **pre-existing**, confirmed by stashing my changes and re-running. No Fusion instance was booted. ### Routing request Per your call: the `moves.ts` group and the final removal of `isWorkflowColumnsCompatibilityFlagEnabled` go to **U2b**, inside the convergence PR where the equivalence proof already lives. The divergences their characterization suite does **not** yet cover: plugin column gates, the `transitionPending` marker, `workflowId` in `task:move` run-audit, and move-policy preflight. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a271f1868f |
U10: dashboard renders workflow-resolved columns (6 legacy-vocabulary defects, incl. a silently-disabled open-PR guard) (#2492)
Phase D / **U10** of the workflow-owned-lifecycle program (**R8**). This
unit **blocks U11** (merge Todo into Planning) — the board must render
IR-resolved columns before the column shape can change.
## What was wrong
Six dashboard surfaces answered a column question from the legacy
`COLUMNS` / `VALID_TRANSITIONS` vocabulary rather than the card's own
workflow IR. Each is a defect today, and each is a way U11 would ship
visibly broken.
| # | Surface | Defect |
|---|---|---|
| 1 | Board — All workflows | Appended **every** legacy column id to the
lane union with synthesised flags → a phantom lane for a column no
workflow declares, labelled with the raw id, ordered by the enum index
with an alphabetical tie-break that scrambled a custom workflow's
declared order |
| 2 | ListView | `if (groups[column])` **silently dropped** a row whose
stored column the workflow no longer declares — no lane, no row, no
error |
| 3 | Move menu | A card stranded in an undeclared column got an **empty
move list** — the one surface that could rescue it offered nothing |
| 4 | Task Detail | Header badge rendered the raw stored id;
title/description editing gated on the literal `{triage, todo}` — a
renamed planning lane lost Edit with nothing on screen to explain it |
| 5 | `board-workflows` | The built-in lifecycle label map was an
**override**, not a fallback, so it replaced a name a built-in
deliberately chose |
| 6 | `POST /tasks/:id/move` | The open-PR backward guard used
`COLUMNS.indexOf(...)` → **-1 on any renamed board**, and the guard
treats a negative index as "allow" |
**#6 is the one worth reading twice.** The guard did not start rejecting
the wrong things — it stopped existing. On a renamed board an operator
could drag a card backward out of review with an open GitHub PR,
orphaning it, and nothing failed. This is precisely the "a converted
guard silently stops firing" row in the plan's risk table, reached
through a rename rather than a conversion.
The re-engage copy of that same guard is **deliberately left on the
legacy enum**, with a comment saying why: it is gated on literal
`in-review` / `in-progress` end to end, so converting only its indices
would make it *weaker* (a workflow declaring `in-review` but not
`in-progress` would score -1 and disable it). U5 owns that lane.
## Evidence
**Every fix has a test that fails when the fix is reverted.** With the
six production files stashed and the tests kept, **10 of the 28 tests
fail**:
- Board aggregate: phantom lane present (2)
- ListView: stranded card dropped, desktop **and** mobile (2)
- Move menu: empty move list for a stranded card (1)
- Task Detail: badge shows `staging`, Edit missing in a renamed intake
**and** hold lane (3)
- `board-workflows`: `builtin:lead-generation`'s `triage` renders as
"Planning" (1)
- Move route: backward move between renamed columns **allowed** with an
open PR (1)
The other 18 are regression pins on behaviour that must not change
(default-workflow lane order and labels, legacy `in-review →
in-progress` block, legacy editable columns, forward moves, terminal
PRs).
**Measured, not estimated.** The label-map clobber was quantified
against the built-in IRs actually in tree: **4 column names replaced — 3
case-only variants ("In progress" → "In Progress"), 1 genuine semantic
rename.** Only the rename is a user-visible defect; the fix preserves
the case normalisation rather than churning the default board.
## Surface enumeration (AGENTS.md)
Desktop **and** mobile — the breakpoint is `(max-width: 768px),
(max-height: 480px)`, so landscape phones exceed 768 wide and match on
height. Column states: empty, populated, duplicate id across two
workflows, and a column no workflow declares. Views: single-workflow
lane, All-workflows aggregate, list, move menu, task detail, move route.
## Regression check
Full dashboard suite, both sides of the change:
| | Test Files | Tests |
|---|---|---|
| Before | 41 failed / 1068 | **296 failed** / 21195 |
| After | 42 failed / 1071 | **297 failed** / 21223 |
`+28` total is exactly the tests this change adds. The single failure
delta is `register-model-routes-kimi-k3-supplemental`, which **fails
identically on this branch's base when run in isolation** — shard-order
dependent, unrelated to columns. **Zero regressions attributable to
U10.** The ~296 pre-existing dashboard failures are inherited from main
and are flagged to the coordinator, not touched here.
`pnpm test:gate`, `pnpm lint`, both dashboard typechecks
(`tsconfig.json` and `tsconfig.app.json`), `pnpm smoke:boot`, and `pnpm
check:changesets` are green.
## Not in this unit
- `Board`'s legacy single-lane `COLUMNS.map` fallback still exists.
`MainContent` passes `workflowColumnsEnabled` unconditionally, so it is
unreachable in-app, but proving that is a deletion argument and this is
not a deletion unit — flagging rather than removing.
- `ListView`'s `LEGACY_LIST_COLUMNS` fallback, same reasoning.
- `flagEnabled` on the wire (U2 noted U10 retires it once no client
reads it) — four clients still branch on it; retiring it is a
client-shape change that belongs with U11's shape work.
- `GET /api/tasks?column=` still validates against `COLUMNS`, rejecting
a workflow-declared custom column as a list filter. Server-side filter
surface, not a rendering decision.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
43160a7aae |
FN-8621: migrate complex modals to FloatingWindow
Unify complex dashboard modal presentation under persisted FloatingWindow geometry. - Migrate Create Room, Task Detail, Agent Detail, and GitHub Import modal presentations. - Preserve documented embedded and docked exceptions, dismissal behavior, and nested scrolling. - Add presentation-contract coverage and publish dashboard guidance and changesets. Files changed: ...n-8619-resize-persist-modals-floating-window.md | 7 ++ .changeset/fn-8621-create-room-floating-window.md | 7 ++ docs/dashboard-guide.md | 16 +++- docs/dashboard-modal-inventory.md | 12 +-- .../dashboard/app/components/AgentDetailView.css | 16 +--- .../dashboard/app/components/AgentDetailView.tsx | 102 ++++++++++++++++----- .../dashboard/app/components/CreateRoomModal.css | 19 +++- .../dashboard/app/components/CreateRoomModal.tsx | 57 ++++++++---- .../dashboard/app/components/FloatingWindow.css | 13 ++- .../dashboard/app/components/FloatingWindow.tsx | 15 +++ .../dashboard/app/components/GitHubImportModal.css | 11 +-- .../dashboard/app/components/GitHubImportModal.tsx | 40 ++++++-- .../dashboard/app/components/TaskDetailModal.css | 52 +---------- .../dashboard/app/components/TaskDetailModal.tsx | 58 ++++++------ .../__tests__/AgentDetailView.core.test.tsx | 2 +- .../AgentDetailView.mobile-scroll.test.tsx | 6 +- .../components/__tests__/CreateRoomModal.test.tsx | 62 +++++++++++-- .../components/__tests__/FloatingWindow.test.tsx | 1 + .../__tests__/GitHubImportModal.test.tsx | 8 +- ...etailModal.responsive-and-dependencies.test.tsx | 77 +++++++--------- .../__tests__/modal-presentation-contract.test.tsx | 74 +++++++++++++++ .../dashboard/app/hooks/useEmbeddedPresentation.ts | 2 +- .../dashboard/app/hooks/useModalResizePersist.ts | 5 + 23 files changed, 441 insertions(+), 221 deletions(-) Fusion-Task-Id: FN-8621 Fusion-Task-Lineage: 04b6f3fe-d527-4a21-a0cb-489eb20f5e91 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
fde3b76a8c |
FN-8602: enable tablet touch resizing for task modals
Enable touch-driven resizing for task modals on tablet viewports. - Add tablet touch resize handles and pointer interactions for task and new-task modals. - Preserve responsive modal sizing while keeping phone layouts fullscreen. - Add unit, browser, and visual regression coverage with updated documentation. Files changed: .changeset/fn-8602-tablet-touch-resize.md | 7 + docs/dashboard-guide.md | 6 +- docs/testing.md | 4 + packages/dashboard/app/components/NewTaskModal.css | 70 ++++++++++ packages/dashboard/app/components/NewTaskModal.tsx | 19 ++- .../dashboard/app/components/TaskDetailModal.css | 12 ++ .../dashboard/app/components/TaskDetailModal.tsx | 7 +- .../app/components/__tests__/NewTaskModal.test.tsx | 4 + ...etailModal.responsive-and-dependencies.test.tsx | 1 + .../hooks/__tests__/useModalResizePersist.test.tsx | 10 +- .../app/hooks/__tests__/useViewportMode.test.ts | 26 +++- .../dashboard/app/hooks/useModalResizePersist.ts | 26 +++- packages/dashboard/app/hooks/useViewportMode.ts | 13 ++ packages/dashboard/app/styles.css | 16 +++ .../app/task-modal-touch-resize-e2e-fixture.html | 5 + .../app/task-modal-touch-resize-e2e-fixture.tsx | 60 ++++++++ .../__screenshots__/fn-8602/phone-fullscreen.png | Bin 0 -> 43987 bytes .../e2e/__screenshots__/fn-8602/tablet-after.png | Bin 0 -> 52549 bytes .../e2e/__screenshots__/fn-8602/tablet-before.png | Bin 0 -> 11500 bytes .../task-modal-touch-resize-browser.test.ts | 154 +++++++++++++++++++++ 20 files changed, 424 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-8602 Fusion-Task-Lineage: f07ce22f-f529-4be3-ba1b-04063154a1b0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
f26cbedf4f |
fix(dashboard): close the code-review findings on the mobile tab-discard work
An 11-reviewer pass over f157bf7460..f5163d8351 found defects in the mobile tab-discard change set itself. This fixes them. Silent data loss (the recurring defect class): - AgentDetailView reconnect refetched limit:100 and replaced wholesale, so 380 displayed lines vanished with no "Load older" and no indicator; it now reconciles through the shared logStreamReconcile helper. - useActivityLog.loadMore past the cap discarded the page it had just fetched while advancing the cursor and leaving hasMore true, so the feed silently stopped paginating behind a live-looking button. - useAgentLogs: loadMore and resyncFromServer had no mutual exclusion, a no-overlap resync discarded explicitly paged-back history, a resync outliving the reconnect delay left an unmarked gap, and the live-tail trim could evict the gap marker itself. - useLiveTranscript's resync overwrote live entries that raced the refetch. The premise itself was not fully delivered: - useProjects, useNodes, and useMeshState never called clearInterval, so they polled the whole time the tab was hidden. useProjects is mounted for the entire session, so the page never went idle -- the primary mechanism this work depends on. All three now use the shared visibility gate. - sse-bus fired onReconnect twice per reconnect cycle and fanned out ~28 subscribers in one tick, against a ~6-connection-per-origin cap on a waking radio. The successful open is now the single authority, and the fan-out uses the same exported stagger primitive as the polling path rather than a second copy of the slot formula. - A channel first subscribed during the hidden window opened a live EventSource and keepalive; suspension is now a module-level condition openChannel consults, and a channel opened inside the grace window re-arms it. Credentials and correctness: - The service worker persisted every GET /api/* to durable Cache Storage, including /api/settings with daemonToken, githubAuthToken, gitlabAuthToken and ntfyAccessToken in plaintext, with no exclusion and no purge path -- "Clear all cached data" only walked localStorage. Now gated, bounded, and genuinely purgeable. - useTasks cleared its own snapshot when the mount revalidation failed on a waking radio, so the board blanked and the next restore was empty too. Suspension-class failures no longer destroy the cache. - A single-row SSE update reset lastFetchTimeMs to now while an hours-old hydrated snapshot was on screen, re-marking every in-progress card stuck. - ListView's "Select all visible tasks" acted on the full filtered set while only 50 rows rendered, so a bulk delete reached rows the operator could not see. Column's search window reset keyed on a boolean, so refining a query kept the expanded window. Tests that could not fail: - App.test.tsx mocked TerminalModal as isOpen ? <div/> : null, making the unmount-on-close invariant unobservable; MockEventSource kept its listeners after close(), so cases passed with their onReconnect handlers deleted. - The SSE resync ratchet scanned only hooks/, exempting ~13 component call sites -- the exact regression it exists to prevent. - MissionControlPanel's bespoke poll and the xterm scrollback constants and WebGL disposal had no coverage at all. Verified: tsc -p tsconfig.app.json clean, pnpm lint clean, pnpm check:changesets clean, 877 tests passing across 36 scoped files. Known unrelated red: MailboxView.test.tsx's FN-8407 CSS guard fails at HEAD too -- this diff adds no @media rule and no .mailbox-view--mobile selector, the only two things that assertion inspects. Left alone deliberately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4b4df1ff30 |
FN-8565: restore tablet task modal resizing
Restore resizable Task Detail and New Task windows on 768px touch tablets. - Classify known touch tablets separately from phone sheets - Restore bounded modal geometry and resize grips with keyboard support - Add regression coverage, documentation, and a patch changeset Files changed: .changeset/fn-8565-tablet-task-modal-resize.md | 7 +++ docs/dashboard-guide.md | 5 ++ .../task-detail-modal-tablet-width.test.ts | 15 +++++ packages/dashboard/app/components/NewTaskModal.css | 18 +++++- packages/dashboard/app/components/NewTaskModal.tsx | 44 +++++++++++++- .../dashboard/app/components/TaskDetailModal.css | 30 ++++++++++ .../dashboard/app/components/TaskDetailModal.tsx | 40 +++++-------- .../app/components/__tests__/NewTaskModal.test.tsx | 68 +++++++++++++++++++++- ...etailModal.responsive-and-dependencies.test.tsx | 51 ++++++++++++++++ .../hooks/__tests__/useModalResizePersist.test.tsx | 53 ++++++++++++++++- .../dashboard/app/hooks/useModalResizePersist.ts | 58 +++++++++++++++--- 11 files changed, 352 insertions(+), 37 deletions(-) Fusion-Task-Id: FN-8565 Fusion-Task-Lineage: 7d9bbeb9-0e6a-4ad9-8b4d-e9f3d8aef650 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
5f3f7709da |
FN-8530: link GitHub Import to source issues
Make GitHub import provenance a direct, single source-issue link. - Link the GitHub Import label whenever source metadata contains a URL. - Preserve a plain provenance label when no source URL is available. - Cover desktop, compact, nonstandard URL, and absent URL rendering. Files changed: .../dashboard/app/components/TaskDetailModal.tsx | 38 +++++++------ .../__tests__/TaskDetailModal.rendering.test.tsx | 66 ++++++++++++++-------- 2 files changed, 64 insertions(+), 40 deletions(-) Fusion-Task-Id: FN-8530 Fusion-Task-Lineage: 03f899c2-2a3b-4b27-9bb6-b4db12667bb0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e24fb37c8b |
FN-8492: keep mobile task footer actions on one row
Keep task-detail footer actions compact and readable across mobile widths. - Prevent mobile footer controls from wrapping while preserving shrink behavior. - Add ellipsis-safe labels for actions and review controls. - Cover standard and in-review footer layouts with responsive tests. - Add a patch changeset for the mobile footer fix. Files changed: .../fn-8492-mobile-task-footer-single-row.md | 7 ++ .../dashboard/app/components/TaskDetailModal.css | 74 ++++++++++++++++- .../dashboard/app/components/TaskDetailModal.tsx | 8 +- ...etailModal.responsive-and-dependencies.test.tsx | 94 +++++++++++++++++++++- 4 files changed, 175 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-8492 Fusion-Task-Lineage: dae7a449-5022-4bfe-a879-880349341594 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ddb5c5eca4 |
FN-8481: enable GitHub tracking for Ideas tasks
Allow operators to manage GitHub tracking from Ideas intake and Coding (Ideas) task details. - Permit GitHub tracking toggles in the Ideas column and the built-in Coding (Ideas) workflow. - Cover fallback and resolved workflow metadata paths with dashboard tests. - Add a patch changeset for the operator-facing capability. Files changed: .changeset/fn-8481-ideas-github-tracking.md | 7 ++++ .../dashboard/app/components/TaskDetailModal.tsx | 13 +++++-- ...lModal.inline-editing-and-integrations.test.tsx | 40 ++++++++++++++++++++-- 3 files changed, 56 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-8481 Fusion-Task-Lineage: d267e203-bceb-492b-b218-8829406ef537 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8ab4241afd |
FN-8476: restore Ideas detail move action
Restore Coding (Ideas) task-detail moves to Todo while preserving supplied workflow field definitions. - Resolve selected workflow columns independently for detail move actions - Use workflow-specific primary action labels and add desktop/mobile regression coverage - Add a patch changeset for the restored detail action Files changed: .changeset/fn-8476-coding-ideas-move.md | 7 ++ .../dashboard/app/components/TaskDetailModal.tsx | 85 +++++++++++++------- .../TaskDetailModal.custom-fields.test.tsx | 9 ++- .../__tests__/TaskDetailModal.rendering.test.tsx | 91 ++++++++++++++++++++++ 4 files changed, 160 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-8476 Fusion-Task-Lineage: 37975987-91e4-47f2-9fa4-57b2074391f9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
57636226b1 |
FN-8421: align tablet task modal control heights
Align Task Detail action controls and Planner Chat composer heights across tablet and desktop. - Apply shared compact-height classes to inline Task Detail actions - Use Quick Add control-height tokens and remove conflicting button padding - Size Planner Chat Send and Stop controls to match the composer - Add dashboard regression coverage and a patch changeset Files changed: .../fn-8421-task-detail-tablet-control-heights.md | 7 ++++++ .../dashboard/app/components/TaskDetailModal.css | 18 ++++++++++++--- .../dashboard/app/components/TaskDetailModal.tsx | 11 ++++++--- .../app/components/TaskPlannerChatTab.css | 10 ++++++++ .../__tests__/TaskDetailModal.rendering.test.tsx | 8 +++++++ ...etailModal.responsive-and-dependencies.test.tsx | 13 +++++++++++ .../__tests__/TaskPlannerChatTab.test.tsx | 27 ++++++++++++++++++++++ 7 files changed, 88 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-8421 Fusion-Task-Lineage: 151795c8-4c00-4976-8901-9446557fc1a3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
237d9be05f | fix(dashboard): de-emphasize empty verification status |