Files
buzz/desktop
ss-dev-02 fe57b7ac7b fix(desktop): settle a work block whose running step has no live session
A tool item's `executing`/`pending` status is written when the step starts
and never revised if the agent dies first, so an abandoned step keeps it
forever. On the activity feed that was a stale row label. In the work
block this commit introduces it is a MODE: one `running` entry makes
`summarizeWorkBlock` report `isActive`, which suppresses the folded
summary line, holds the rail open and pulses a bullet. Scrolling back to
a crashed turn therefore showed the reader live work indefinitely
(Codex on #6536, discussion_r3848214880 — not stale, and worse here than
on the card it was filed against).

Status alone cannot answer "is this step happening?", so the block is
given the missing half: `AgentSessionTranscriptTurnMeta.liveTurnId`,
published by the list from the same display blocks it already reads for
`streamingItemId`. `toolEntryState` treats `executing`/`pending` as
`running` only when a session owns that step's turn.

A turn id, not a boolean. "Some turn is live" is not the question: an
agent that crashed during turn 1 and is now working on turn 2 IS live,
yet turn 1's abandoned step is no more running than before — a global
flag would keep it spinning in exactly the case a restarted agent makes
common. The comparison is explicit (`liveTurnId !== null && ...`) so an
item with no turn id cannot match a null live turn by `null === null`.

An abandoned step is reported as `settled`, not as a new state and not as
`failed`, per core-02:

- We do not know it failed — only that nobody finished it — so it must
  not count toward the folded line's `N failed`. It folds to a neutral
  `N steps`.
- No third entry state and no new glyph. It renders as the neutral step
  it is, with the same muted detail the list already shows for it outside
  a block. A visible "interrupted" marker would be a design addition,
  not a bug fix.

`liveTurnId` is read from the display blocks rather than from the
active-turn store, because the store's turn ids and the transcript's are
populated by different paths and a mismatch would silently gate every
step off — the mirror image of this bug. It is the last *turn* block, not
the last block: a compaction notice arrives as a `single` block after the
turn it belongs to, so reading the final block's kind would report no
live turn at all.

`projectWorkBlockEntries` takes the option as a required argument rather
than defaulting it, so a future caller that has not thought about
liveness cannot silently get the spins-forever behaviour back.

Non-vacuous by mutation, each mutant run in isolation against the unit
suite:

- gate removed (always `running`): 4 failures.
- gate inverted (`executing` never `running`): 5 — this is the fix that
  would have "passed" the bug report while breaking live work.
- truthiness instead of turn ownership (any live turn resurrects an old
  abandoned step): 1.
- `item.turnId === liveTurnId` without the null guard: 1.
- abandoned step reported `failed` instead of `settled`: 4.
- `liveTurnId` forced null in the meta builder: 2.
- `lastTurnId` reading only the final block: 1.

Measured in a real browser as well, not only JSDOM, because the failure
mode is presentational and animation-dependent — a unit test can assert
class names while the rendered rail is still wrong (the interim-note and
relay-send bugs on this branch were both invisible to the unit suite).
Two seeded scenarios in the preview harness, driving the real component
through the cover drawer under Buzz Dark:

- agent panic mid-step (the terminal `crates/buzz-acp` actually emits):
  folded label `3 steps`, rail collapses to 0 rows, `getAnimations`
  reports 0 infinite animations in the block, and opened the rail reads
  `["settled","settled","settled"]`.
- the same events with no panic: no folded line, rail open, states
  `["settled","settled","running"]`, and exactly 1 infinite animation —
  the running bullet really does pulse.

Both browser scenarios were mutation-checked too: removing the gate fails
the first and passes the second, inverting it fails the second and passes
the first, so neither can be satisfied by a one-sided fix. (Those two
specs are not in this commit — they need Slice A's `conversation` variant
pin to be reachable, which is not on this branch.)

The row outside a block is untouched: `buildCompactToolSummary` still
derives its own `running` from status, and no file under
`activityRenderClasses/` or `agentSessionToolSummary.ts` is modified here.

Verified at this tree: full desktop suite 5486 passing / 0 failing,
`tsc --noEmit` clean, `pnpm check` findings identical to main's baseline
(2 warnings + 2 infos, all pre-existing).

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-08-24 18:12:37 -07:00
..
2026-08-03 21:51:17 -04:00
…

Buzz

Desktop chat shell with:

  • Tauri + React + TypeScript + Vite
  • Tailwind CSS
  • shadcn/ui-ready shared components
  • Biome (lint/format/check)
  • Feature-driven frontend structure

Scripts

  • pnpm dev - run the web frontend
  • pnpm tauri dev - run the desktop app
  • pnpm build - typecheck and build frontend
  • pnpm typecheck - TypeScript checks
  • pnpm lint - Biome lint
  • pnpm format - Biome format (write)
  • pnpm check - Biome check

Structure

  • src/shared - reusable app-wide code (ui, lib, styles)
  • src/features - feature modules (vertical slices)
  • src/app - top-level app composition