With tabArrangement 'state' (the default) and lineage lines on (the desktop
default), the spine ran straight through the WORKING / WAITING / NEEDS YOU
label of every row it passed. #538's
`.session-tabs.tabs-triage:is(.tabs-auto-wrap, .tabs-two-rows)` pads the
strip by the label gutter and pulls each label to the strip's edge with a
negative margin; it outranks #544's `.lineage-tree.tabs-auto-wrap`
`padding-left: 20px`, so the spine channel disappeared, and the spine,
anchored at the strip's left edge (computeLineageTree), sat in the label
column.
The channel now opens BETWEEN the label column and the tabs: with lineage,
the state strip pads by gutter + --lineage-spine-channel, every label keeps
its column at the edge (still under the brand) and hands the channel back
after itself, and the lead label still starts right after the brand.
computeLineageTree takes the channel's left edge as `spineLeft`, which
session-lineage.js reads back from the padding the CSS laid out (content edge
minus the channel), so the two cannot drift; without it, or where the channel
is the padding itself (classic, ledger, case), the spine stays at the strip's
edge exactly as before. The spine's clamp now only considers tabs in rows the
spine can run beside (every row after the first), so a first row that starts
left of the channel (a quiet idle group right after the brand) no longer
drags the spine back over the labels.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drawing only the selected tab's family hid every other connection until you
clicked into one. All families are drawn again (still as routed trees); the
selected tab's families (what it spawned, and the family it was spawned into)
get .lineage-family--focus: 2.5px instead of 1.5px, full opacity instead of
0.75, a larger end dot, and drawn last so nothing covers them.
Lanes follow strip order (cycling through CodemanLineage.MAX_LANES, 3), never
the selection, so a family's lines never move when you click a tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lineage lines used to draw one glowing dashed bezier per parent/child pair,
hanging below the tab strip, for every family at once. With a parent on row 3
of a wrapped strip and ten children below it, the curves crossed every lower
row's tab names and ran through the terminal text.
- One rounded orthogonal tree per spawning tab: every route starts at the
parent, so siblings share a trunk. 1.5px, solid, 1px dark outline; only a
working child's branch is dashed.
- Routes run only through the gaps between tab rows, joined by a spine left of
every row (computeLineageTree/computeLineageRows in constants.js), so they
never cross a tab or reach the terminal. The vertical rail gets the same tree
on its existing left track.
- Only the selected tab's family is drawn (what it spawned, plus its parent and
siblings). Selection redraws, and the strip's size transitionend redraws once
more, since the active tab widens for ~150ms after the selection redraw.
- The routing room is reserved by .session-tabs.lineage-tree, keyed on whether
any lineage exists, never on the selection, so a tab switch never resizes the
header or the PTY.
- Colours stay per spawning tab, now claimed in strip order for every family so
selection order never decides who gets the skin blue.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The vertical tab rail now reads the owner's tab layout (GET /api/tab-layout)
and draws its groups as collapsible sections. This is the first frontend
consumer of the tab-layout backend and it is read-only: nothing in the
browser writes the layout yet.
- tab-layout-browser.js (new, pure, loaded before app.js): projects the
layout onto the live sessions and open web tabs, renders the grouped
markup, stores collapse per device, and sequences loads newest-wins with
a bounded retry on failure.
- app.js: loads the layout on init and on tab:layoutChanged, renders the
grouped rail from the same per-row markup the flat rail uses, falls
through to a full render whenever the grouping structure changes, and
withholds drag-reorder in the grouped rail.
- Grouping is opt-in by construction. With no layout, a failed read, a
layout without groups, or a horizontal strip, the rail renders exactly
as before (byte-identical markup).
- Grouping is a render layer only: sessionOrder, Alt+N, Ctrl+Tab and the
palette keep reading the server-projected order, and row badges keep
their Alt+N slot.
- A collapsed group still shows the active row; lineage arcs to a hidden
session anchor to its group header.
- webview-tabs.js: renderWebviewTab() extracted so a single web tab can be
placed into its group with unchanged markup.
Post-merge follow-ups from the deep review of #334 and #335, so they ship in
the same release as the features.
Tab-layout foundation (#335):
- PUT /api/session-order drops unknown/foreign ids again instead of 400ing
the whole write, in both the owner and the admin path (single-user requests
are the synthetic admin, so that path is the one the browser hits). The
frontend debounces its reorder push and swallows errors, so a session
deleted inside the debounce window silently cost the user the entire
reorder - and the endpoint sits on the stable /api/v1 surface, where the
pre-layout server merged leniently.
- A failed mux restore no longer locks explicit deletions into 500s for the
process lifetime: runSessionDeletion and webviewDeleted degrade to
best-effort without layout coordination, while the automated stale sweep
(runStaleSessionCleanup) stays fail-closed.
- sse-events doc comment: no 'suppressed' hook event exists; hooks stay 8.
- registerSessionWithLayout resolves its owner through ownerLayoutKey()
instead of a hardcoded '@single'.
Vertical rail (#334) - all rail-awareness gaps in sidebar-only predicates,
unified behind the new _isVerticalTabList() (sidebar OR rail):
- Drag-reorder read the insertion side from clientX in the rail, so
before/after was effectively arbitrary on vertical rows; the drag-over
indicators now draw as top/bottom edges there like the sidebar's.
- The active tab is scrolled into view in the rail (Alt+N/palette selection
used to leave the row below the fold).
- Floating subagent/ultracode windows anchor to the RIGHT of rail tabs, and
the connector redraw gates (render tail + strip scroll) cover the rail.
- Server-seeded tabOrientation is applied when the async settings load
resolves, not only at boot, so a fresh device shows the rail immediately.
- The pre-paint script stamps data-tab-orientation and --tab-rail-width
(sidebar-wins and solo carve-outs included), removing the flash of the
header strip on every vertical-mode load.
- The session name font defaults to 12px, the sidebar's historical 0.75rem
size, so installs that never touch the new slider are not restyled.
Also documents the rail in CLAUDE.md (second #sessionTabs host, mover
ordering, the axis-predicate rule) and gives tab-rail-resize.js its
@dependency/@loadorder header. Full gate green (6093 tests); the excluded
browser suite was run by hand - only the known environmental failures
(opencode/codex binaries) remain, identical to pristine master.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lineage arcs were coloured per child, so one tab's own workers each got a
different colour, which is the distinction the colours exist to make. The colour
is now keyed on the parent: every arc leaving one tab is the same colour however
many workers it spawns, so the strip reads as "these five came from w1, those
two came from w2". A child that spawns in turn is a parent in its own right and
gets its own colour for the arcs below it, so a chain changes colour at each
generation while each generation's fan-out stays uniform.
The new tests drive the real _appendLineageConnectionLines() and assert the
painted custom property, because testing the colour function alone passes just
as happily with the child id passed back in.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings in https://github.com/christianhaberl/Codeman/pull/4 (three commits,
authorship preserved) and adapts it across the 211 commits master gained
since the branch was cut:
- App Settings control re-authored for the set-* surface (PR #278): a
set-row in Layout -> Tabs, replacing the old settings-item markup the
branch targeted. i18n description synced.
- Lineage arcs (PR #291, post-branch) are SKIPPED in sidebar layout:
computeLineagePath()'s U-bridge geometry hangs from the horizontal
strip's bottom edge and has no meaning against a vertical list. The
lineage strip-scroll listener now also redraws subagent/ultracode
connectors while the sidebar scrolls vertically.
- The desktop home tab rail (post-branch) defers to the sidebar: both dock
the session list flush left, and the rail would render z-ordered under it.
- Active-row reveal unified into _scrollActiveTabIntoView() (#257 landed on
master after the branch): sidebar mode branches to scrollIntoView
block:'nearest', and _fullRenderSessionTabs() restores scrollTop alongside
the #257 scrollLeft restore so ambient rebuilds cannot yank a mid-scroll
sidebar back to the top.
- Mobile active-tab hoisting the branch guarded against no longer exists on
master (removed by #257); kept master's order-stable render.
Verified: typecheck, lint, format:check, check:frontend-syntax,
check:public-assets, PostCSS parse of both merged stylesheets, the 26 new
jsdom tests, the structural guard suites, and the headless-Chromium harness
(scripts/verify-session-sidebar.mts) green across all seven layout states
at 1600/1000/393px against current master.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SKILL.md: forbid the standalone preamble check and pre-spawn recon turns
(measured: two wasted model turns cost ~12s of a 28s two-worker run; the
hardened flow measured 20.2s cold / 12.8s warm end to end)
- Lineage lines: dip now hangs from the strip's bottom edge (cap 104 -> 64,
no stacked row offsets), fixing the deep bow in wrapped strips and keeping
row-1 arcs off row-2 tab labels; per-child color palette (skin blue first,
then matrix green, pink, violet, red, turquoise, orange) via an inline
--lineage-color custom property
- Session Options -> Session: per-TAB pop-out (open-in-window) button override
on top of the general showTabDetachButton setting; per-device localStorage
map rendered as the tab-show-detach class
- Tab alerts: seed the pending-hook state machine from GET /api/approvals
regardless of the approvals-inbox setting (reloads used to lose the red tab
entirely with the inbox off), clear unconditionally on approval_resolved,
and repaint the alert as a steady red/yellow ring + glow + status dot on a
::before overlay so it stays visible on the selected (active) tab until the
permission is actually resolved
- docs: worker warm-pool design sketch (verified numbers baked in)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lines that join a tab to the workers its codeman skill spawned were
drawn with numbers tuned against two tabs sitting side by side, and they
degraded in exactly the two situations the feature is actually used in.
1. A spawned worker is appended to the END of the strip, so the real span
between a lead and its worker is 800-1500px. With the dip clamped at
44px that is a 33px sag: the arc reads as a straight line drawn across
the terminal instead of a bracket hanging under the strip. The dip now
grows at 0.085/px and clamps at 104.
2. When the desktop strip wraps (tabs-two-rows / tabs-auto-wrap), a parent
on row 1 and its child on row 2 are ~14px apart, and the cross-row
branch drew parent-bottom to child-TOP: a flat line hidden inside the
row gap, with siblings overprinting each other. Both ends now anchor on
the tab BOTTOM with the control points below the LOWER row, so a wrapped
pair gets the same bracket a flat strip gets. That deletes the branch:
one shape covers both.
Visibility, at 1:1 rather than in a zoomed mockup: 2 -> 2.5px stroke,
4 4 -> 5 5 dashes (lineage-flow moves with them, -16 -> -20), opacity
.55 -> .72, and a second wider glow so the contrast comes from the halo
rather than from more weight, keeping the line under the subagent lines'
3px. A working child is bright (.95) outside the reduced-motion block, so
turning motion off no longer also dims every worker's arc. Sibling nesting
6 -> 8px and the direction dot 3 -> 3.5px to match the heavier stroke.
Verified at 1:1 in a harness driving the real styles.css and the real
computeLineagePath over three layouts (adjacent workers, workers at the
far end of a full strip, wrapped two-row strip) on a dark and a light
skin. test/session-lineage-lines.test.ts pins both regressions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>