Commit Graph
4 Commits
Author SHA1 Message Date
Codeman maintainer 33f77c4680 fix(tabs): review fixes for the detailed rail — width-dialog default, compact wrap pass, rich-aware resets
Three review findings on the detailed-rows feature, all in its edge cases:

- The App Settings width select consulted the handheld defaults blob
  (tabRailWidth: 256) BEFORE the rich-aware default, which the renderer
  never reads — so a tablet's unsized rich rail rendered 320 while the
  dialog said 256, and a routine Save persisted the 256 (below the 288px
  tight threshold, permanently). The chain now mirrors
  applyTabRailWidth()'s actual resolution.

- _setTabRailWidth() re-rendered on a compact flip but never re-ran
  applyTabWrapSettings(), the one owner of the folder line, whose railRich
  input reads the compact class this function just toggled. A rich rail
  dragged below 240px kept emitting folder rows — persistently, for a
  stored width < 240, since the boot wrap pass runs before the class is
  first applied. The wrap pass now re-runs on the flip, with exactly one
  render either way.

- Both reset affordances (handle dblclick, Enter on the handle) reset to
  the hardcoded 256 even on a rich rail, landing it below the tight
  threshold; both now resolve the rich-aware default (320), via a new
  optional defaultWidth input on resolveTabRailKeyboardWidth().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 18:26:07 +02:00
Codeman maintainer b330f1d9e8 feat(tabs): give the vertical rail the home screen's per-session detail
The vertical tab rail (tabOrientation 'vertical') listed names and nothing
else, while the rich sidebar and both home screens already answered the
question a docked column exists to answer: which of these sessions wants me
next, and how long has it been like that. The rail is a docked column too, so
it now draws the same row.

- New per-device setting tabRailDetail ('rich' | 'simple', default rich),
  App Settings -> Appearance -> Tabs, in SettingsUpdateSchema + displayKeys and
  stamped as data-tab-rail-detail by the pre-paint script, so a detailed rail
  does not flash through simple rows on every load.
- ONE gate for both vertical surfaces: isRichTabRows() =
  isSessionSidebarRich() || isTabRailRich(). The row model, the markup and the
  20s in-place clock are the existing rich-sidebar ones, classified by
  _mobileOverviewState/_mobileOverviewSince, so the rail, the sidebar, the
  desktop home rail and the phone overview cannot disagree about what
  "working" means or which stamp measures it.
- Detail rides on its OWN attribute, exactly as the sidebar's does, so every
  existing [data-tab-orientation='vertical'] rule keeps matching both variants
  untouched. A flip of detail ALONE still forces a full render (the stamps line
  is emitted by the row template, not toggled by CSS) and re-runs
  applyTabWrapSettings(), which owns the folder line and is now rail-aware.
- CSS: every rich paint rule gains a rail twin as a COMMA-GROUPED selector,
  never :is() - an :is() list takes its most specific argument, which would
  lift the sidebar arm from (0,3,1) to the rail's (0,5,1) and let these rules
  outrank things they never used to.
- Width is why there are thresholds. At 256px the stamps line ellipsizes
  mid-word, the same reason the rich sidebar is 300px, so a rail that has never
  been sized defaults to 320 (RICH_DEFAULT_WIDTH, the existing Wide preset,
  which also keeps the settings select on a named choice). A width the user has
  chosen is never overridden: below 288px the created stamp is dropped rather
  than truncated (tab-rail-tight, CSS only) and below 240px the rows go back to
  simple (tab-rail-compact, which re-renders).
- The rich clock is armed and disarmed by applyTabOrientation() as well as
  applySessionListLayout(); a leaked interval would rewrite stamps in a list
  that no longer has any.

Also fixes a data-loss bug in the inline tab rename that predates the rail and
reproduces in every layout, header strip included: Escape set the input to ''
and blurred it, and the blur handler commits - so cancelling a rename PUT an
empty name, and the tab fell back to its folder label (measured against a live
server: ["rail-alpha","","rail-gamma"]). Escape now calls cancelRename(), which
invalidates the edit so the blur that follows the input's removal is a no-op.

Tests: rail-detail gate, the three ways it turns back off (simple, compact,
horizontal), sidebar-wins, render-on-detail-flip and the plumbing/CSS guards in
test/session-list-layout.test.ts; the rename cancel in test/inline-rename.test.ts
(browser suite), pinned by running it against the old code first. Verified live
against a real server on an isolated instance: detailed/simple/compact/header/
sidebar variants, click-select, the ... menu, inline rename, Alt+N, the in-place
stamp tick and a full settings-picker round-trip including reload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 04:45:02 +02:00
Codeman maintainer 7a340fe7bc fix(tabs): review-driven hardening for the tab-layout foundation and vertical rail
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>
2026-08-24 00:59:48 +02:00
Aamer Akhter e3a2fb767f feat(tabs): COD-358 add resizable vertical session rail 2026-08-23 12:21:02 -04:00