Batched conversion of every lifecycle-column guard I hold, plus the
three the census could not see. **Six files to zero, repo-wide 60 → 49
by a comment-stripped unanchored sweep.** Each conversion has an
isolated revert proof and a paired negative case, and the one code move
is a separate commit from the behavior changes.
## Per-file before → after
Counts from a comment-stripped, unanchored `(===|!==) ["']triage["']`
sweep over `packages/*/src` + `plugins/*/src`, excluding tests.
| file | before | after | note |
|---|---:|---:|---|
| `core/default-workflow-hooks.ts` | 4 | **0** | |
| `core/task-store/moves.ts` | 5 | **4** | only the flag-ON mirror
converted; the flag-OFF inline block is the parity reference and stays |
| `engine/executor.ts` | 3 | **0** | **absent from the 45-guard list** —
see below |
| `core/live-agent-count.ts` | 2 | **0** | duplication removed; answer
deliberately unchanged |
| `engine/replan-target.ts` | 2 | **0** | both were comment prose, not
guards |
| `core/agent-prompts.ts` | 3 | **0** | ROLE comparisons, never column
guards |
| `engine/usage-limit-detector.ts` | 2 | **0** | ROLE comparisons |
| `dashboard/app/components/DocumentsView.tsx` | 1 | **0** | real column
guard |
| `dashboard/app/components/TaskChatTab.tsx` | 2 | **0** | ROLE |
| `dashboard/app/components/AgentLogViewer.tsx` | 1 | **0** | ROLE |
| `dashboard/app/components/effective-model-resolution.ts` | 1 | **0** |
ROLE |
| `dashboard/app/hooks/useTasks.ts` | 1 | **0** | ROLE |
| `dashboard/…/command-center/MissionControlPanel.tsx` | 1 | 1 | alias
table, marked `DELIBERATE-LITERAL` with its reason |
## The census errs in BOTH directions
This is the finding I would most like carried into the remaining work.
- It **flagged 10 sites that were never column guards.** `role ===
"triage"` / `agentType === "triage"` compare an **AGENT ROLE**. The
planner *lane* is named `triage` and keeps that name — U11 removed the
*column*. Worse than noise: the obvious "finish the migration" edit is
to rename the role, and that silently empties the planner's prompt
template and mis-binds its model markers. `PLANNER_AGENT_ROLE` now names
it, so the two vocabularies are distinguishable by grep and a rename
fails loudly (revert proof: 4 tests, two of them pre-existing).
- It **missed 3 real guards in `executor.ts`**, because the pattern
matches `column`/`toColumn`/`fromColumn` and those locals are named
`from` and `originColumn`. A census keyed on variable names will keep
missing guards wherever a local was named for its role in the function.
## Two real defects, not tidying
**1. A renamed board could merge with its re-review never run.**
`default-workflow-hooks.ts` is named for the default workflow, but the
store runs it on the flag-ON path for *every* workflow — the trait
registry resolves hooks by trait id, not by workflow. Its reopen
predicates listed the default lineage's column names, so on a renamed
board **no reopen effect fired at all**. One of them clears
`workflowStepResults`, which `getTaskMergeBlocker` reads: a card bounced
out of review carried its old `passed` result back in, and that
satisfies the merge gate. Same regression the graph-owned-crossing
carve-out exists to prevent, arriving through the other door. (Two
smaller ones rode along: failure state never cleared on a renamed
reopen, and an operator dragging a card back to the queue never parked
it, so the scheduler re-dispatched what they had just pulled back.)
**I forgot the carve-out on my first pass, and that was worse than not
converting.** A role-resolved clear plus a *name*-matched exemption
means a renamed board takes the clear and never the exemption,
destroying the remediation input the graph had just written. My own
paired negative test caught it.
**2. The last-resort recovery for completed-but-stranded work did not
exist off the default lineage.** In `recoverCompletedTask`,
`promotedFromPlannerColumn` was false on a renamed board, so finished
work resting in the planning lane was never promoted — the code fell
through to `handoffTaskToReview` straight from the planning column, and
role adjacency has no planning → review edge, so the handoff was
rejected and the card stayed stuck with its work complete. I converted
the promotion **target** too: resolving the lane and then moving to a
literal `in-progress` is the half-conversion I have already been burned
by twice this program, where the guard starts admitting cards and the
move then sends them to a column the board does not declare.
## E2E evidence
`renamed-board-reopen.pg.test.ts` drives a **real PostgreSQL store** and
a real `moveTask` on a workflow whose columns carry the standard traits
under non-default names. The unit tests cannot show this: if `moves.ts`
passed `undefined`, every unit case still passes via the no-basis
fallback while the real board keeps the old behavior. **Proof it is
load-bearing: forcing `moveLifecycleColumns` to `undefined` fails 2 of
3.** The executor suite covers both the split-role and the MERGED
post-U11 shape.
## Revert proofs, isolated per site
| change reverted | result |
|---|---|
| reopen predicate → literal names | 4 of 10 fail |
| reopen field clears → literal names | 2 of 10 fail |
| `userPaused` hold lane → literal `todo` | 1 of 10 fail |
| graph carve-out → literal names | 1 of 10 fail |
| store passes `undefined` lifecycle columns | 2 of 3 fail (real PG) |
| `promotedFromPlannerColumn` → literals | 3 of 7 fail |
| two-hop condition → `=== "triage"` | 1 of 7 fails |
| promotion target → `"in-progress"` | 3 of 7 fail |
| `isPlannerColumnFor` → literals | 1 of 7 fails |
| live-agent-count: one arm dropped | 2 of 11 fail |
| DocumentsView: trait branch removed | 3 of 7 fail |
| planner role renamed to `"planner"` | 4 fail (2 pre-existing) |
Every conversion is paired with a negative case (a forward move, a
not-a-planner-lane card, a default-lineage card, a renamed column with
no traits), so neither "always fire" nor "never fire" can pass for
"resolve the role".
## Deliberately NOT converted, with reasons
- **`moves.ts` flag-OFF inline block (4).** That branch *is* the legacy
path, kept verbatim so the two can be parity-checked. Converting it
erases the reference implementation.
- **`live-agent-count.ts`'s no-flags fallback.** Reachable, and there is
nothing to resolve from — `enrich…FromFlags` exists for callers with
board flags rather than an IR, so a column missing from that map is the
renamed case. "Not intake" is as much a guess as "todo is intake", and
Running/Waiting are complements, so a card matching neither arm is
reported as neither and the footer's queued total under-reports it. The
real fix is at the caller; four new cases pin that flags override the
legacy answer **in both directions**. What did change is the
duplication: two hand-written copies of one rule now call one named
function.
- **`MissionControlPanel`'s `FUNNEL_STAGES`.** An alias table of column
*names* where `triage` sits beside `signal` and `backlog`. Command
Center aggregates across projects, so there is no single workflow to
resolve traits from — the honest conversion is a data change, not a
predicate change.
- **`DocumentsView` with no traits.** Same no-basis rule; the documents
list is full of historical columns absent from the current board. A case
asserts a renamed column with no traits still reads as "working",
documenting the gap rather than hiding it.
## Fixture findings
Each cost a red run that looked like the code under test:
- a `merge-blocker` column needs a reachable merge-class node, or
`parseWorkflowIr` rejects the workflow;
- a back-edge must be `kind: "rework"`, and a rework edge is legal only
**into** a node with `config.reworkRegion: true`;
- a workflow gets role-level transitions only when it declares wip +
review + complete + **archived** plus a planning lane — without the
archived column, adjacency falls back to order-derived neighbours and
`checking -> queued` is not a legal move at all;
- `recoverCompletedTask` only *reaches* the promotion seam when nothing
is left to gate; without passed `plan-review`/`code-review` rows it
re-enters the workflow graph and returns first, so a naive fixture
silently tests the wrong branch and every assertion reads "no moves
happened" for an unrelated reason.
## Verification
- `pnpm test:gate` **71/71**
- new suites: 10/10 reopen-semantics, 3/3 renamed-board-reopen (real
PG), 7/7 executor-planner-lanes, 7/7 documents-status-dot, 4/4
planner-role-is-not-a-column
- neighbours: 132 + 10 + 482 (gate shards), 350/351 engine
planning/replan suites, 64/64 agent-prompts, 51/51 usage-limit-detector,
11/11 live-agent-count, 11/11 dashboard hook/log suites
- the single engine failure (`executor-fast-mode-workflows.test.ts` ›
"raw fast mode still invokes non-executable review seam nodes")
**reproduces with my changes stashed** — pre-existing on `origin/main`
- typechecks clean for core, engine, and dashboard-app
(`tsconfig.app.json`; `tsconfig.json` checks nothing under `app/`);
`pnpm lint` clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Keep live chat and log viewers from snapping readers back to the bottom while streamed content grows.
- Track pinned-bottom state synchronously across task chat, general chat, and agent logs.
- Follow streamed DOM and layout growth only when the reader remains at the tail.
- Add regression coverage and a patch changeset.
Files changed:
.changeset/fn-8339-chat-scroll-follow.md | 7 +++
.../dashboard/app/components/AgentLogViewer.tsx | 52 +++++++++++--------
packages/dashboard/app/components/ChatView.tsx | 4 ++
packages/dashboard/app/components/TaskChatTab.tsx | 59 ++++++++++++++++------
.../__tests__/AgentLogViewer.layout.test.tsx | 58 ++++++++++++++++++++-
.../__tests__/ChatView.streaming-thread.test.tsx | 18 ++++++-
.../app/components/__tests__/TaskChatTab.test.tsx | 34 +++++++++++++
7 files changed, 195 insertions(+), 37 deletions(-)
Fusion-Task-Id: FN-8339
Fusion-Task-Lineage: 8f1290ea-de87-4edc-b343-f50c0ba55bad
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Ensure task chat role icons reflect each agent's actual runtime model.
- Parse runtime model markers from status and text log entries.
- Prefer runtime and effective models over stale task provider overrides.
- Cover provider icon precedence and fallback behavior for all chat roles.
Files changed:
packages/dashboard/app/components/TaskChatTab.tsx | 8 +-
packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx | 88 ++++++++++++++++++----
2 files changed, 76 insertions(+), 20 deletions(-)
Fusion-Task-Id: FN-8214
Fusion-Task-Lineage: 1eb02ca5-469f-4a9d-932c-5cf02383b7fe
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Show Activity Live reasoning immediately for active and review-stage tasks.
- Default thinking blocks open for in-progress and in-review tasks.
- Preserve collapsed, user-toggleable reasoning for all other task columns.
- Document behavior and cover default state and user collapse interactions.
Files changed:
.changeset/fn-8171-thinking-default-open.md | 7 ++++
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.tsx | 20 +++++++---
.../app/components/__tests__/TaskChatTab.test.tsx | 43 ++++++++++++++++++++--
4 files changed, 63 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-8171
Fusion-Task-Lineage: 705d6ac8-0c43-47d3-84df-57e9ec55a3b5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
A rate-limited reviewer filled a task's Chat tab with 14 identical
"Reviewer using model: ..." markers and no review text, hammering an
already-limited provider.
Root cause: the reviewer was the only AI lane that never classified
provider errors, so a 429 became an UNAVAILABLE verdict. With no
validator fallback configured the fallback ladder re-ran the SAME model
instantly, and fn_review_step answered with "code review remains
blocking; retry once" — bounding the loop with prompt text rather than
code. The tool's catch-all also swallowed the error into tool output, so
withRateLimitRetry, UsageLimitPauser and RetryStormError never fired.
- reviewer: throw ReviewerProviderError for usage-limit/transient errors
instead of laundering them into UNAVAILABLE, and never spend the
fallback budget (which bounds bad reviews) on an outage.
- reviewer: absorb flaky-network blips in-lane via withRetry with
jittered backoff; rate limits still escalate immediately.
- executor: re-raise the fatal after the prompt via
throwDeferredReviewerFatal — pi-agent-core converts tool throws into
tool_error results, so a tool cannot throw out of session.prompt().
- executor: give code review a real MAX_CODE_REVIEW_UNAVAILABLE_RETRIES
counter, mirroring the plan/spec limiter.
- reviewer: dedupe the model marker on text, so same-model retries stay
silent while a genuine model switch still emits.
Also fixes the run-on rendering: AgentLogType gains `status` for complete
engine messages. `text` means "streamed delta" and is re-glued with
join(""), which is why N standalone markers rendered as one string. The
split is at the type, not a separator — a separator would reintroduce the
FN-5787/5789/5803 streamed-spacing regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renames the task-detail Activity Current segment to Live and makes expansion available across Activity views.
- relabel Activity Current as Live while preserving the legacy internal current segment routing
- move the Activity expand/collapse control to the shared Activity toolbar for Live, Feed, and Raw Logs
- update expanded Activity layout, docs, tests, and release notes for the shared behavior
Files changed:
.changeset/fn-7325-activity-live-expand.md | 7 ++
docs/dashboard-guide.md | 8 +-
packages/dashboard/app/components/TaskChatTab.tsx | 6 +-
.../dashboard/app/components/TaskDetailModal.css | 53 ++++++++++-
.../dashboard/app/components/TaskDetailModal.tsx | 103 ++++++++++++---------
.../app/components/__tests__/TaskChatTab.test.tsx | 12 +--
.../TaskDetailModal.attachments-and-tabs.test.tsx | 90 ++++++++++++++++--
.../__tests__/TaskDetailModal.rendering.test.tsx | 2 +-
.../components/__tests__/TaskDetailModal.test.tsx | 8 +-
9 files changed, 213 insertions(+), 76 deletions(-)
Fusion-Task-Id: FN-7325
Fusion-Task-Lineage: 1aee503b-ace8-4238-a9f9-3e536a43c1f8
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The idle session hint banner is suppressed (empty) per user request — idle chats stay sendable but no longer show the banner. Done/active hints remain. Test updated to assert the banner is absent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prevent task chat from showing idle guidance while planner sessions are actively working.
- Treat triage/planning task states as active steering sessions when they are not queued, paused, or blocked.\n- Keep queued, awaiting, failed, and paused states on idle guidance even in active columns.\n- Cover inline and expanded planning chat surfaces across empty, populated, and loading transcripts.\n- Update the dashboard guide to document planning and live session bridge steering behavior.\n\nFiles changed:\n docs/dashboard-guide.md | 2 +-\n packages/dashboard/app/components/TaskChatTab.tsx | 24 +++++++---\n .../app/components/__tests__/TaskChatTab.test.tsx | 51 +++++++++++++++++++---\n 3 files changed, 66 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-6866
Fusion-Task-Lineage: 48c57515-40fa-40c1-ae3b-fec0690bba75
- TaskChatTab: add FNXC:TaskDetailChat prefix/date to the ephemeral
active-session comment, matching the file's comment convention.
- useModalManager: clear stale planning/subtask resume-session id (and
initial plan) when opening a fresh planning/subtask flow, so the modal
no longer reopens into a prior session.
- boardWorkflowsCache: validate cached workflow item shape (id/name/
columns) and taskWorkflowIds value types so a malformed cache entry
can't pass and later crash Board on render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Reliability (P3): emit an `Auto-recovered:`-prefixed log on the
benign-todo stale-failure clear path so NotificationService
proactively cancels the pending failure timer (recoveredStatus path)
instead of relying only on the fire-time re-check, which is
race-contingent when failureNotificationDelayMs is near 0. Scoped to
the actual-clear path so the common no-failure re-queue isn't
mislabeled as a recovery.
- Project-standards (P3): add the required yyyy-MM-dd-hh:mm stamp to the
new FNXC comments (AGENTS.md FNXC_LOG convention).
- Maintainability (P3): extract the scheduler "queued" waiting marker to
a named SCHEDULER_WAITING_STATUS constant.
- Testing: pin the guard's skip on a clean todo row, assert the
Auto-recovered log fires on the stale-failure path, and add a
paused+unassigned in-progress idle case (paused early-return wins over
the ephemeral active-session path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the in-progress fix for in-review: a reviewer/merger runs
ephemerally with no assignedAgentId/checkedOutBy, so an in-review task
in an active review/merge status (reviewing, merging, merging-fix,
fixing) now reads as a live session without an assignment. A null-status
in-review row is awaiting human review, not actively worked, so it stays
assignment-gated and idle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
isActiveAgentSession required a persistent assignedAgentId/checkedOutBy,
but the scheduler only sets those when ephemeralAgentsEnabled === false
(scheduler.ts:1857). The default is ephemeralAgentsEnabled: true
(settings-schema.ts:351), so an actively-executing in-progress task
never has either field and always read as idle, showing the
"No agent is working on this task right now" hint while an agent was
running. sessionLive only covers live CLI sessions, not ephemeral runs.
Treat assignment as sufficient-but-not-necessary: a non-blocked,
non-"queued" in-progress task is a live agent session on its own.
"queued" (the waiting marker) stays assignment-gated and in-review is
unchanged, so existing FN-6314 idle/active behavior is preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make task-detail chat disclose when sent guidance will not receive an immediate agent reply.
- Add an idle session hint for non-done tasks without an active steerable agent while keeping the composer sendable.
- Style the idle hint with warning color and expose a stable test id for regression coverage.
- Expand TaskChatTab tests across inline, expanded, empty, populated, paused, and non-live session states.
- Document the idle saved-guidance behavior in the dashboard guide.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.css | 5 ++
packages/dashboard/app/components/TaskChatTab.tsx | 13 ++-
.../app/components/__tests__/TaskChatTab.test.tsx | 95 +++++++++++++++++++---
4 files changed, 99 insertions(+), 16 deletions(-)
Fusion-Task-Id: FN-6751
Fusion-Task-Lineage: 21f0764d-23db-49ca-b737-3b6a8be7a918
Keep the task chat expand action visible as an icon-only overlay inside the chat view.
- Move the expand/collapse button out of the scrolling transcript and onto the task chat container.
- Style the control as a centered icon-only button with stable overlay positioning.
- Extend TaskChatTab coverage for empty, loading, populated, expanded, and scrolled transcript states.
Files changed:
packages/dashboard/app/components/TaskChatTab.css | 10 +++-
packages/dashboard/app/components/TaskChatTab.tsx | 26 +++++-----
.../app/components/__tests__/TaskChatTab.test.tsx | 58 +++++++++++++++++-----
3 files changed, 68 insertions(+), 26 deletions(-)
Fusion-Task-Id: FN-6425
Fusion-Task-Lineage: 6526eb4a-4be9-472b-9595-c7fcea6971b1
Enable the task-detail Chat composer to submit messages with standard Enter behavior.
- Submit non-empty task chat drafts on plain Enter while preserving Shift+Enter newline entry and IME composition safety.
- Keep Cmd/Ctrl+Enter as a supported send path through the same handler.
- Document the composer keyboard shortcuts and add dashboard tests for steering, refinement, newline, shortcut, and composition behavior.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/tiny-tasks-chat-enter.md | 5 +
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.tsx | 13 ++-
.../app/components/__tests__/TaskChatTab.test.tsx | 120 +++++++++++++++++++++
4 files changed, 136 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-6415
Fusion-Task-Lineage: 7d200bf6-5130-4fb8-b59c-66bccffe4265
Moves the task chat expand control into the transcript as an overlay while preserving accessible labels and state coverage.
- Render the expand/collapse button inside the transcript container instead of a separate toolbar.
- Add tokenized overlay positioning and mobile sizing styles for the transcript expand control.
- Extend TaskChatTab tests across loading, empty, populated, and CSS states to prevent toolbar regressions.
Files changed:
packages/dashboard/app/components/TaskChatTab.css | 34 +++++++++---------
packages/dashboard/app/components/TaskChatTab.tsx | 22 ++++++------
.../app/components/__tests__/TaskChatTab.test.tsx | 42 ++++++++++++++++++++--
3 files changed, 67 insertions(+), 31 deletions(-)
Fusion-Task-Id: FN-6405
Fusion-Task-Lineage: 344d3ad0-bd57-4df2-86dc-deb8cddb76bc
Add a full-modal expansion affordance for task-detail chat conversations.
- Add an expand/collapse toolbar button to the task chat tab with accessible labels and icon states.
- Let the task detail modal switch into a chat-expanded layout and reset that state when leaving chat or entering edit mode.
- Cover the chat toggle and layout behavior with dashboard component tests and document the control.
Files changed:
docs/dashboard-guide.md | 1 +
packages/dashboard/app/components/TaskChatTab.css | 22 +++++
packages/dashboard/app/components/TaskChatTab.tsx | 21 ++++-
.../dashboard/app/components/TaskDetailModal.css | 42 +++++++++
.../dashboard/app/components/TaskDetailModal.tsx | 13 ++-
.../app/components/__tests__/TaskChatTab.test.tsx | 37 ++++++++
.../TaskDetailModal.attachments-and-tabs.test.tsx | 100 +++++++++++++++++++++
7 files changed, 233 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-6370
Fusion-Task-Lineage: 787300bb-b928-45cf-a5c1-7fed5f375111
Refine the task-detail chat composer so the send action stays narrow and inline with the input.
- Replace the composer placeholder with the steering-focused copy.
- Convert the send button to an accessible icon-only control with loading state labels.
- Keep the send control inline at mobile breakpoints and cover the behavior in tests and docs.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.css | 14 +++++++----
packages/dashboard/app/components/TaskChatTab.tsx | 11 ++++++---
.../app/components/__tests__/TaskChatTab.test.tsx | 27 ++++++++++++++++++++--
4 files changed, 44 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-6369
Fusion-Task-Lineage: b7e0717f-4cba-4380-a08a-e01fc12985e3
Adds an in-transcript control for returning to the newest task chat output after reviewing older messages.
- Track whether the task chat transcript is near the bottom and preserve live-follow behavior.
- Render an accessible sticky Latest button when populated transcripts are scrolled up, including mobile styling.
- Cover empty/loading, desktop, mobile, and click-to-jump behavior in TaskChatTab tests.
- Document the Latest jump affordance in the dashboard guide.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.css | 40 +++++++++++
packages/dashboard/app/components/TaskChatTab.tsx | 40 ++++++++++-
.../app/components/__tests__/TaskChatTab.test.tsx | 82 ++++++++++++++++++++++
4 files changed, 162 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-6359
Fusion-Task-Lineage: 9b4a3533-2969-4f0d-a4de-6ba895662726
Task-detail Chat now accepts guidance even when no steerable session is active and queues it for the next run.
- Keep the task chat composer enabled whenever there is draft text and no send is in flight.
- Replace the blocking no-session hint with active-session versus queued-for-next-session copy.
- Extend TaskChatTab coverage across idle, paused, inactive CLI, and send-in-flight states.
- Quarantine the unrelated flaky dashboard settings route test observed during broad verification.
Files changed:
packages/dashboard/app/components/TaskChatTab.tsx | 21 ++--
.../app/components/__tests__/TaskChatTab.test.tsx | 135 +++++++++++++++++----
packages/dashboard/vitest.config.ts | 5 +-
scripts/lib/test-quarantine.json | 5 +
4 files changed, 133 insertions(+), 33 deletions(-)
Fusion-Task-Id: FN-6354
Fusion-Task-Lineage: b0f0d033-bb4d-4eaa-a15d-f06b82643ade
Display user steering comments alongside agent output and wake immediate-response agents when new messages are sent.
- Render persisted and optimistic user messages in the task chat transcript with mobile styling.
- Propagate updated task detail data after sends and roll back optimistic messages on failures.
- Cover steering route validation, immediate heartbeat wakeups, chat rendering, and workflow alias round-trips.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-6345-task-chat-user-messages.md | 5 +
packages/dashboard/app/components/TaskChatTab.css | 32 ++++
packages/dashboard/app/components/TaskChatTab.tsx | 170 ++++++++++++++++-----
.../dashboard/app/components/TaskDetailModal.tsx | 8 +-
.../app/components/__tests__/TaskChatTab.test.tsx | 150 +++++++++++++++++-
.../app/components/__tests__/board-mobile.test.tsx | 2 +-
.../__tests__/workflow-flow-mapping.test.ts | 4 +-
.../app/components/workflow-flow-mapping.ts | 4 +-
.../src/__tests__/routes-tasks-ops.test.ts | 86 +++++++++++
9 files changed, 414 insertions(+), 47 deletions(-)
Fusion-Task-Id: FN-6345
Fusion-Task-Lineage: 0355cc67-bd23-4e90-9034-bbaae277cfd6
Task detail chat now renders adjacent text log chunks as continuous bubbles.
- Combine consecutive non-tool, non-thinking chat entries within each agent role group before rendering.
- Preserve tool and thinking segment boundaries while simplifying segment keys for grouped text.
- Add dashboard tests for single, consecutive, and cross-role text bubble behavior.
- Document the text chunk grouping behavior in the dashboard guide.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.tsx | 28 ++++++++-----
.../app/components/__tests__/TaskChatTab.test.tsx | 48 ++++++++++++++++++++++
3 files changed, 66 insertions(+), 12 deletions(-)
Fusion-Task-Id: FN-6344
Fusion-Task-Lineage: e88f0383-7d91-4272-8c9b-532a382074d4