SIGN IN SIGN UP

feat(web): reach a tab or a pane by name, and stamp history in the reader's zone

The session view had one flat row of the OTHER panes in the workspace, tagged
`Tab` and `Space`, each chip named `label || pane_id`. On the measured host 28
of the 30 panes carry no operator label, so that row read `w6:pH  w6:pQ  w6:pR`
-- three chips whose names differ by one character, no mark for the pane you
were standing in, and no way to reach a tab by name at all.

It is now herdr's own two levels below the space, ported from Collie's TabStrip
+ PaneStrip: the tabs of this space, then the panes of this tab.

- A pane is named by what it can still say for itself (paneChipName, after
  Collie's paneDisplayName): the operator's label, then an agent's activity
  title, then its harness; a terminal's cwd basename, then `shell`. Never
  `project` -- the relay sets that to basename(cwd), which every pane in one
  worktree shares. The pane id's suffix rides along as a muted tag, because a
  name is routinely shared by every chip in the row (three shells in `herdr`,
  three claudes in one tab) and the suffix is the only part that separates them.
- The open pane and its tab are filled and carry aria-current. Blue is
  selection everywhere else on this page, and the old row had no `you are here`.
- Tapping a tab lands on tabLandingPane -- the neediest agent in it by
  bucketOf, else its first terminal. A tab with no pane the client can name is
  not a chip; the tab you are in is inert rather than closing your open panel.
- Each row is hidden when it holds no choice: tabs unless the space has two
  reachable ones (6 of 10 agent panes measured are in a single-tab space), panes
  unless the tab holds a second (10 of 10 do). 66px together, 7.8% of 390x844.
- Neither row is labelled in words: a `Tabs` / `Panes` label cost 40px of the
  row, and 4 of the 5 rows that scrolled on the real host overflowed by less
  than that. The name is on the strip's aria-label; the distinction is shape
  (a tab chip is squarer) and the id tag a tab has not got. A chip's height is
  fixed at 22px because a CJK activity title is taller than latin at the same
  size and grew the row 2px; the name is capped with the rest in the tooltip.

And three fixes to the history panel:

- A turn is stamped in the READER's zone. Claude writes every transcript
  timestamp in UTC -- all 4,450 rows sampled here end in `Z` -- and this was
  ts.slice(11, 16), so a turn made at 17:08 in UTC+8 read 09:08. The date rides
  along when the turn is not from today, because paging back is the point of the
  panel and a bare 18:51 cannot say which day it is.
- The header is one row. It was a title bar over a filter bar, 80px of a 390x844
  screen spent before a single turn rendered; the Find chip now opens the input
  in place of the title, 35px in both states, and closing it drops the needle.
- The list carries overscroll-behavior: contain, like .term-content already did.
  A drag at the top chained to the document and handed Chrome its
  pull-to-refresh, which reloads the app -- losing the session, the panel, and
  however far back you had paged.

tests/test_web_spaces.py rewrites the strip suite around the two rows (49
tests); tests/test_web_history.py adds WebHistoryPanelTests, which pins the page
to Asia/Shanghai and reopens it as America/Los_Angeles because "the reader's
zone" is the claim and the runner's clock would make it a test of the runner. It
also drops that file's second playwright instance for the module-shared browser
its siblings use -- two in one process is the contention that times out goto.
N
Nick007 committed
41926e427912d07c73af736eba5499c8a4e54e98
Parent: 23c1de2