Skip to content

fix(web): parked fleet threads list in Settled and Snoozed, compact rows keep titles - #166

Merged
ImZoomBoy merged 2 commits into
mainfrom
fix/settled-workers-to-settled-section
Sep 25, 2026
Merged

ImZoomBoy merged 2 commits into
mainfrom
fix/settled-workers-to-settled-section

Conversation

@ImZoomBoy

@ImZoomBoy ImZoomBoy commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Settled fleet workers filled the sidebar. Since #162, every settled or snoozed fleet thread stayed in the fleet tree as a dimmed full row.

What changed

Parked fleet threads now leave the tree and list in the sidebar's own sections.

  • A settled worker lists in Settled. Un-settling it puts it back under its second mate, in launch order.
  • A snoozed worker lists in Snoozed. Waking it puts it back under its second mate.
  • A settled second mate moves to Settled and takes all its workers out of the tree. Each worker lists by its own state: a settled one in Settled, a snoozed one in Snoozed, a live one in Active or Pinned. Un-settling the second mate brings it and its live workers back into the tree.
  • A worker whose repository has no live second mate follows the same worker rules.
  • A snoozed second mate stays in the tree, dimmed, with its wake control, as before.
  • Nothing else in the tree changes.

Why live workers of a settled second mate go to Active, not Settled: every Settled row carries Un-settle. A worker that is not settled has nothing to un-settle, so that control would do nothing. It also keeps a running worker in view.

How

  • resolveParkedState in fleetSidebar.logic.ts is the one parking rule. Snooze outranks settlement until the thread wakes. A server without the capability never parks a thread. The tree and the section loop in Sidebar.tsx both call it, through parkedStateOf, so they cannot drift apart.
  • sidebarSectionFor picks the section for a thread outside the tree. The section loop uses it.
  • buildFleetTree now requires parkedState, so a caller cannot forget it.
  • A drag out of Settled or Snoozed counts as landed before the server confirms it. A dragged worker goes straight back into the tree instead of showing in Active first. The drop hold waits for the server's confirmation when the thread lands in the tree, because it never joins the Active list.

Compact rows keep their titles

In Settled and Snoozed, a read-only worker row showed no title. The model and thinking badges never shrank, and the role word was cut to one letter. Compact rows now keep the whole role word and leave the model and thinking level to the hover card. Full rows still show the badges.

fork-features.json

The fm-fleet-sidebar-tree description and keep line name the new behaviour, resolveParkedState, sidebarSectionFor, the parkedStateOf helper in Sidebar.tsx, the drop hold, and the compact row. The read-only model entry says a compact row leaves the badges to the hover card. The user guide's First Mate section says where parked fleet threads go.

Screenshots

Taken headless from a dev build run against a copy of real data. They show real thread titles, so they are kept locally and not attached.

  • Before: the tree holds 2 second mates with 28 settled workers under them, and 7 more settled workers with no second mate. The tree runs past the bottom of the window, and Settled is off screen.
  • After: the tree holds First Mate, 2 second mates and the 1 worker that is not settled. Settled sits at the bottom with the settled workers.
  • Close-up of Settled: every row shows its title. Worker rows show the whole word "Worker". Rows with a pull request badge show a shorter title, still readable.

The data copy has no snoozed thread and no settled second mate, so the screenshots cannot show those cases. The tests cover them.

Tests

The new tests fail on the previous commit of this branch and on main, then pass. I ran the new test file against each older copy of fleetSidebar.logic.ts:

== logic from HEAD (previous commit)
     × lists a settled worker in Settled, not in the tree
     × lists a snoozed worker in Snoozed, not in the tree
     × puts a worker back under its second mate when it is un-settled or woken
     × takes every worker out of the tree with a settled second mate, each to its own section
     × brings a second mate and its live workers back when the second mate is un-settled
     × puts snooze ahead of settlement until the thread wakes
     × never parks a thread on a server without the capability
     × follows a drag before the server confirms it
     × puts a settled worker dragged into Active back in the tree at once
      Tests  9 failed | 12 passed (21)
== logic from main
      Tests  9 failed | 12 passed (21)   (the same 9)

The settled worker test now checks the section it lands in, Settled, not only that it left the tree. A test also pins that a snoozed second mate stays in the tree with its workers. The compact row fix has no unit test. The screenshot above is its check.

Checks

$ (apps/web) vp test run
 Test Files  410 passed (410)
      Tests  5472 passed (5472)

$ (apps/web) vp run typecheck
exit 0

$ vp lint --report-unused-disable-directives
exit 0. Warnings only. Sidebar.tsx has the same warnings as on main.

$ vp fmt --check <5 changed files>
All matched files use the correct format.

$ node scripts/check-fork-features.ts
 Test Files  1 failed | 20 passed (21)
      Tests  11 failed | 449 passed (460)

The 11 failures are all in scripts/build-desktop-artifact.test.ts. They fail on main on this Windows machine too:

$ node scripts/check-fork-features.ts   (with main's files)
 Test Files  1 failed | 20 passed (21)
      Tests  11 failed | 440 passed (451)

Surfaces

Web and desktop. The mobile sidebar has no fleet tree and is unchanged.

A settled worker thread now leaves the fleet tree and lists in the
Settled section with the other settled threads, where Un-settle works.
Un-settling it puts it back under its second mate. Snoozed workers and
settled second mates stay in the tree as before.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Sep 25, 2026
@ImZoomBoy

Copy link
Copy Markdown
Owner Author

Code review, two axes. Each finding has a confidence and an estimated severity.

Standards

Six findings: two hard violations, both in the PR body, and four judgement calls in the code. The code itself breaks no documented standard.

PR body (hard violations)

  1. No before/after images. AGENTS.md (Pull requests) and CONTRIBUTING.md both require images for a UI change. The body describes the screenshots but does not attach them, because they show real thread titles. The reason is fair, but the rule is still broken. Screenshots from redacted or seeded data would satisfy it. Confidence high, severity medium.
  2. The body does not end with the model and harness. AGENTS.md says "End with the model and harness that did the work." The fork owner's own rules may override this one. Confidence medium, severity low.

fork-features.json (judgement call)

  1. The keep line leaves out the parkedState helper in Sidebar.tsx. It names "the isSettled option passed to buildFleetTree", but that option calls parkedState. docs/agents/upstream-conflict-resolution.md rule 3 says "Do not keep a fork hunk the sentence does not cover." Read strictly, a merge could keep isSettled and drop the helper it calls. Confidence medium, severity medium.

apps/web/src/components/Sidebar.tsx (judgement calls)

  1. Duplicated Code. parkedState repeats the section loop's snooze-then-settle checks a few lines further down:

    • if (capabilities?.threadSnooze === true && effectiveSnoozed(...)) return "snoozed"; if (capabilities?.threadSettlement === true && thread.settledOverride === "settled") return "settled";
    • The loop has else if (supportsSnooze && effectiveSnoozed(...)) ... else if (supportsSettlement && thread.settledOverride === "settled").

    The comment "Same order as the sections below" admits the two must stay in step by hand. The loop could call parkedState once per thread. Confidence high, severity low.

  2. Mysterious Name. The local parkedState function has the same name as the parkedState option of buildFleetRows, and each returns something different. A reader can mix them up. Confidence low, severity low.

apps/web/src/components/sidebar/fleetSidebar.logic.ts (judgement call)

  1. Speculative Generality. options: { readonly isSettled?: ... } = {} is optional, with a default, but every caller passes it: the sidebar and the tests' treeFor. If a future caller forgets it, settled workers quietly stay in the tree, which is the bug this PR fixes. A required parameter would catch that. Confidence medium, severity low.

No findings

  • fleetSidebar.logic.test.ts: the tests check behaviour, not wiring, as AGENTS.md asks. One tiny point: the test writes the type as "settled" | null instead of reusing FleetParked. Confidence low, severity low.
  • docs/user/thread-sidebar.md: the text uses the product's voice and changes only because using the feature changed. That meets the documentation rules.
  • Commit and PR shape: the title uses the conventional format, and the PR has one concern.

Spec

PR 166 does what the spec asked. A settled worker leaves the fleet tree, lists in Settled, and goes back under its second mate when un-settled. The main problem is a display defect in Settled that this change now shows on most rows. I checked it against the code and the local screenshot.

(a) Missing or partial

  1. "a settled worker thread is not in the fleet tree and is in the settled list." The new tests check that the worker lands in the list of threads left out of the tree, which is not the same as the Settled list. The code that sorts those threads into Settled is not tested. Confidence: medium. Severity: low.

(b) Not asked for

  1. "Change only which section a settled worker thread is listed in." A settled worker whose repository has no live second mate now also leaves the tree. The PR says so. It fits "A settled worker thread leaves the fleet tree", so I read it as in scope. Confidence: medium. Severity: low.

(c) Implemented, but looks wrong

  1. "listed with the other settled threads, where its un-settle control still works." In Settled, a worker row loses its title. I confirmed this in after-settled-zoom.png: the rows show "V", then "claude-opus-5-...", then "low", with no title.
    • Cause: in the compact row, the title is set to take only leftover space (Sidebar.tsx:1562). The model and thinking badges never shrink (ReadOnlyThreadModel.tsx:28-29, up to 160px together). The role word "Worker" keeps its full width. So the title gets nothing, and "Worker" is also cut to one letter.
    • This change makes it much worse: before, settled workers sat in the tree and never used these rows. Now dozens do.
    • The fix is small: let the badges shrink in compact rows, or hide them there.
    • Confidence: high. Severity: medium.
  2. "What happens to a settled second mate thread (which has its workers under it)... keep today's behaviour." The settled second mate stays in the tree. Its settled workers now leave it, while before they stayed under it. That follows the worker rule, but it changes what sits under a settled second mate, and the done note does not name it. Confidence: medium. Severity: low.
  3. The tree's "is it settled" test matches the Settled section's rule: same capability checks, same clock, snooze outranks settlement. One gap: when a settled worker is dragged out of Settled, it shows first in Active or Pinned, then jumps to the tree once the server confirms. Confidence: medium. Severity: low.

Checked and fine

  • Snoozed workers stay in the tree, dimmed. A new test pins this.
  • A worker that is both snoozed and settled stays in the tree as snoozed.
  • Nothing else in the tree changed.
  • The fork-features.json description and keep line are accurate.
  • The user guide sentence is accurate.
  • The before and after screenshots exist in C:/Users/Glyn/AppData/Local/Temp/fm-settled-tree/screens/.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.5 KiB −4 B (−0.0%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +2 B (+0.0%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.4 KiB 6.4 KiB −6 B (−0.1%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 56.1 KiB 56.1 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 7 7 0 (0.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.5 KiB −1 B (−0.0%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB +5 B (+0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.4 KiB 6.4 KiB −6 B (−0.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 56.9 KiB 56.9 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 7 7 0 (0.0%) 21 ✅

Baseline: d923d89 · PR result: 8bec146 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 114.0 KiB
  • Claude decoded thread snapshot: 114.7 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

A snoozed worker now lists in Snoozed, and waking it puts it back under
its second mate. A settled second mate moves to Settled and takes all its
workers out of the tree, each listed by its own state. Un-settling it
brings it and its live workers back. A snoozed second mate stays in the
tree.

resolveParkedState is the one parking rule for the tree and the
sections. A drag out of Settled or Snoozed lands a worker in the tree at
once instead of passing through Active.

Compact rows in Settled and Snoozed keep the whole role word and leave
the model and thinking level to the hover card, so the title always
shows.
@ImZoomBoy ImZoomBoy changed the title fix(web): settled workers list under Settled, not in the fleet tree fix(web): parked fleet threads list in Settled and Snoozed, compact rows keep titles Sep 25, 2026
@github-actions github-actions Bot added size:L and removed size:M labels Sep 25, 2026
@ImZoomBoy

Copy link
Copy Markdown
Owner Author

This round, in commit 8bec146:

  • Compact rows in Settled and Snoozed keep their title and the whole role word. The model and thinking level move to the hover card there.
  • Snoozed workers list in Snoozed and return under their second mate when woken.
  • A settled second mate moves to Settled with all its workers. Each worker lists by its own state. Its live workers go to Active or Pinned, because a Settled row's Un-settle means nothing to them. Un-settling the second mate brings it and its live workers back.
  • One parking rule, resolveParkedState, now serves the tree and the sections. buildFleetTree requires it.
  • A worker dragged out of Settled goes straight to the tree, without showing in Active first.
  • A test checks that a settled worker lands in Settled, not only that it leaves the tree.
  • fork-features.json names the parkedStateOf helper and the rest of the new code.

Left:

  • Images stay out of the pull request. They show real thread titles, and the repository owner does not allow uploading them. The body describes them.
  • No model or harness line. The repository owner does not allow one.
  • A snoozed second mate stays in the tree. Nothing asked to move it.

@ImZoomBoy
ImZoomBoy merged commit c0ed528 into main Sep 25, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant