@workerdeck/ui 2.7.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +30 -30
  2. package/build/{SessionPanel-B5FfiBxE.mjs → SessionPanel-BClL7zUS.mjs} +694 -413
  3. package/build/SessionPanel-BClL7zUS.mjs.map +1 -0
  4. package/build/{SessionPanel-_lQLhWOw.d.mts → SessionPanel-BVbau2eR.d.mts} +68 -4
  5. package/build/format.mjs +1 -1
  6. package/build/index.d.mts +22 -15
  7. package/build/index.mjs +6 -6
  8. package/build/index.mjs.map +1 -1
  9. package/build/scoped.css +26 -2
  10. package/build/{status-BO9wJloK.mjs → status-kmsBhuFG.mjs} +3 -3
  11. package/build/status-kmsBhuFG.mjs.map +1 -0
  12. package/build/workspace.d.mts +3 -2
  13. package/build/workspace.mjs +7 -8
  14. package/build/workspace.mjs.map +1 -1
  15. package/package.json +18 -18
  16. package/src/components/agent/Composer.tsx +86 -84
  17. package/src/components/agent/ContextDialog.tsx +1 -1
  18. package/src/components/agent/EntryIcon.tsx +1 -1
  19. package/src/components/agent/FileTree.tsx +1 -1
  20. package/src/components/agent/FileViewer.tsx +1 -1
  21. package/src/components/agent/HostFilesDialog.tsx +2 -2
  22. package/src/components/agent/PermissionModeSelect.tsx +2 -2
  23. package/src/components/agent/PermissionPrompt.tsx +2 -2
  24. package/src/components/agent/SessionBrowser.tsx +1 -1
  25. package/src/components/agent/SessionItem.tsx +4 -2
  26. package/src/components/agent/SessionPanel.tsx +306 -226
  27. package/src/components/agent/SessionWorkspace.tsx +3 -0
  28. package/src/components/agent/SkillsDialog.tsx +3 -3
  29. package/src/components/agent/StatusBar.tsx +1 -1
  30. package/src/components/agent/TaskList.tsx +2 -2
  31. package/src/components/agent/TranscriptRows.tsx +1 -1
  32. package/src/components/agent/UsageDialog.tsx +1 -1
  33. package/src/components/agent/UsageMeters.tsx +17 -4
  34. package/src/components/agent/composer-commands.ts +142 -0
  35. package/src/components/agent/use-path-links.ts +2 -7
  36. package/src/components/agent/use-subagent-frame.ts +2 -2
  37. package/src/components/agent/use-transcript-jumps.ts +2 -2
  38. package/src/components/prompt-area/clipboard-helpers.ts +2 -2
  39. package/src/components/prompt-area/cursor-helpers.ts +7 -7
  40. package/src/components/prompt-area/dom-helpers.ts +8 -8
  41. package/src/components/prompt-area/html-to-markdown.ts +2 -2
  42. package/src/components/prompt-area/prompt-area-engine.ts +4 -4
  43. package/src/components/prompt-area/prompt-area-list-ops.ts +10 -10
  44. package/src/components/prompt-area/prompt-area.tsx +2 -2
  45. package/src/components/prompt-area/trigger-presets.ts +6 -6
  46. package/src/components/prompt-area/types.ts +6 -6
  47. package/src/components/prompt-area/use-chip-editing.ts +8 -8
  48. package/src/components/prompt-area/use-markdown-mode.ts +3 -3
  49. package/src/components/prompt-area/use-prompt-area-events.ts +3 -3
  50. package/src/components/prompt-area/use-prompt-area-keydown.ts +3 -3
  51. package/src/components/prompt-area/use-prompt-area-state.ts +4 -4
  52. package/src/components/prompt-area/use-prompt-area.ts +5 -5
  53. package/src/components/prompt-area/use-trigger-search.ts +1 -1
  54. package/src/components/terminal/PermissionPrompt.tsx +4 -4
  55. package/src/components/terminal/affordances.tsx +1 -1
  56. package/src/components/terminal/file-link.tsx +20 -0
  57. package/src/components/terminal/height.ts +2 -2
  58. package/src/components/terminal/items.tsx +2 -2
  59. package/src/components/terminal/markdown.tsx +31 -5
  60. package/src/components/terminal/scrubber.tsx +1 -1
  61. package/src/components/terminal/todos.ts +1 -1
  62. package/src/components/ui/Splitter.tsx +1 -1
  63. package/src/index.ts +11 -1
  64. package/src/lib/context-note.ts +3 -3
  65. package/src/lib/file-link.ts +70 -0
  66. package/src/lib/format.ts +2 -2
  67. package/src/styles/scoped.entry.css +2 -2
  68. package/src/styles/terminal.css +94 -79
  69. package/src/styles/theme.css +39 -39
  70. package/build/SessionPanel-B5FfiBxE.mjs.map +0 -1
  71. package/build/status-BO9wJloK.mjs.map +0 -1
@@ -1,12 +1,12 @@
1
1
  /*
2
- * Build entry for the SCOPED stylesheet (`@workerdeck/ui/scoped.css`) — see
2
+ * Build entry for the SCOPED stylesheet (`@workerdeck/ui/scoped.css`) - see
3
3
  * `scripts/build-scoped-css.mjs`, which compiles this with the Tailwind CLI and
4
4
  * then rewrites every selector under `.wd-root`. Not shipped as-is and not an
5
5
  * export: embedders import the compiled `scoped.css` artifact.
6
6
  *
7
7
  * `source(none)` + explicit `@source`: the utilities compiled here are exactly
8
8
  * the ones this package's own components (and streamdown, whose markdown
9
- * renderer ships Tailwind-classed markup) use — hermetic, independent of any
9
+ * renderer ships Tailwind-classed markup) use - hermetic, independent of any
10
10
  * host app's Tailwind build.
11
11
  */
12
12
  @import 'tailwindcss' source(none);
@@ -5,7 +5,7 @@
5
5
  * *renderer*: every row it draws lands on the same character cell, and a grid
6
6
  * that has to be reassembled from `gap-2 pl-[calc(...)] -mx-1` at each call site
7
7
  * is a grid that drifts the first time someone edits one row. The components in
8
- * `components/terminal/` are therefore thin — they choose a class and a tone,
8
+ * `components/terminal/` are therefore thin - they choose a class and a tone,
9
9
  * and the geometry lives here in one place where it can be read as a whole.
10
10
  *
11
11
  * It also means an embedder gets the geometry without configuring Tailwind's
@@ -18,12 +18,12 @@
18
18
  * face is exactly one cell, so a gutter is `2ch` and an indent level is
19
19
  * `2ch`, and text lands on a column rather than near one. A wrapped line
20
20
  * hangs under its own first character because the body is its own grid
21
- * column — not because a padding and a negative text-indent were tuned
21
+ * column - not because a padding and a negative text-indent were tuned
22
22
  * against each other.
23
23
  * 2. **Vertical measures are whole multiples of `--term-line`, never padding.**
24
24
  * Blank space between blocks is a *blank line* (an empty row), the way it is
25
25
  * in a terminal. Nothing has margins, so no two blocks can disagree about
26
- * the rhythm, and a row's height is always an integer number of lines — the
26
+ * the rhythm, and a row's height is always an integer number of lines - the
27
27
  * thing that keeps text off half-pixels and out of the blur.
28
28
  *
29
29
  * `--term-font-size` and `--term-line` must both be whole pixels. A line height
@@ -33,8 +33,8 @@
33
33
  */
34
34
 
35
35
  [data-terminal] {
36
- /* Metrics. Overridable per host — VS Code hands its editor font down, and a
37
- phone wants a larger cell — but always in whole pixels. */
36
+ /* Metrics. Overridable per host - VS Code hands its editor font down, and a
37
+ phone wants a larger cell - but always in whole pixels. */
38
38
  --term-font-size: 13px;
39
39
  --term-line: 18px;
40
40
  /* One indent level, and the width of a gutter. The same value on purpose:
@@ -51,8 +51,8 @@
51
51
  font-feature-settings:
52
52
  'liga' 0,
53
53
  'calt' 0;
54
- /* `ch` is the advance of `0`, so tabular figures keep digit columns — the
55
- diff's line numbers — genuinely aligned. */
54
+ /* `ch` is the advance of `0`, so tabular figures keep digit columns - the
55
+ diff's line numbers - genuinely aligned. */
56
56
  font-variant-numeric: tabular-nums;
57
57
  -webkit-font-smoothing: antialiased;
58
58
  tab-size: 2;
@@ -96,7 +96,7 @@
96
96
  --term-diff-fg: #9a9a9a;
97
97
  --term-diff-num: #5a5a5a;
98
98
 
99
- /* A band of code or output. Barely there — one step off the canvas. */
99
+ /* A band of code or output. Barely there - one step off the canvas. */
100
100
  --term-band-bg: rgb(255 255 255 / 0.04);
101
101
  /* The user's own prompt row. */
102
102
  --term-user-bg: rgb(255 255 255 / 0.05);
@@ -104,7 +104,7 @@
104
104
  /* Behind an OPEN block. Yellow, not neutral, and the theme's one deliberate
105
105
  reuse of that tone for something other than "waiting on you": an open block
106
106
  is a state the reader put the transcript into, and on the phone it wants
107
- the same colour as the rail mark that says so. Kept very low — this washes
107
+ the same colour as the rail mark that says so. Kept very low - this washes
108
108
  whole regions, and at band strength an opened run would shout louder than
109
109
  anything inside it. */
110
110
  --term-open-wash: rgb(215 186 125 / 0.1);
@@ -150,7 +150,7 @@
150
150
  min-height: var(--term-line);
151
151
  }
152
152
 
153
- /* Indent levels. A nested row steps one cell per level — the same 2 columns a
153
+ /* Indent levels. A nested row steps one cell per level - the same 2 columns a
154
154
  marker occupies, so a child's marker sits under its parent's first letter. */
155
155
  .term-row[data-indent='1'] {
156
156
  padding-left: var(--term-cell);
@@ -163,7 +163,7 @@
163
163
  }
164
164
 
165
165
  .term-gutter {
166
- /* `pre` so a space-only gutter keeps its cell — an unmarked row must still
166
+ /* `pre` so a space-only gutter keeps its cell - an unmarked row must still
167
167
  start its text on the same column as a marked one. */
168
168
  white-space: pre;
169
169
  user-select: none;
@@ -172,7 +172,7 @@
172
172
  drag that *begins* on an unselectable node starts no selection at all, so
173
173
  without this the most natural way to select transcript text did nothing.
174
174
  Letting the press fall through to the row keeps `user-select: none` doing
175
- its real job — keeping the glyph out of the copied text — without owning
175
+ its real job - keeping the glyph out of the copied text - without owning
176
176
  the pointer. The row below is still pressable from here, since the press
177
177
  handler lives on the ancestor. */
178
178
  pointer-events: none;
@@ -188,7 +188,7 @@
188
188
  overflow-wrap: break-word;
189
189
  }
190
190
 
191
- /* A blank line — the only vertical spacing in the theme. Exactly one row tall
191
+ /* A blank line - the only vertical spacing in the theme. Exactly one row tall
192
192
  and holding nothing, which is what a terminal's blank line is. */
193
193
  .term-blank {
194
194
  height: var(--term-line);
@@ -197,7 +197,7 @@
197
197
  /* A row `scrollIntoView`ed lands one line below the top edge rather than flush
198
198
  against it, so the target reads as the start of something rather than as
199
199
  content cut off by the frame. Only the recap's `'center'` jump still goes
200
- through scrollIntoView — the scrubber's `'start'` jumps compute their own
200
+ through scrollIntoView - the scrubber's `'start'` jumps compute their own
201
201
  offset (a sticky row's rect is wherever it is pinned) and land the line at
202
202
  exactly the top edge. */
203
203
  [data-terminal] [data-slot='transcript-rows'] [data-index] {
@@ -215,7 +215,7 @@
215
215
  /* ── Bands ───────────────────────────────────────────────────────────────────
216
216
  *
217
217
  * A run of rows carrying a background: a code block, a command's output, a diff
218
- * hunk. Full-bleed — the wash reaches the scroller's edges rather than stopping
218
+ * hunk. Full-bleed - the wash reaches the scroller's edges rather than stopping
219
219
  * at a content gutter, because in a terminal the line *is* the full width.
220
220
  * `--term-bleed` is how far out that is; the transcript sets it to its own
221
221
  * padding and the band cancels it with matched negative margin and padding.
@@ -264,13 +264,13 @@
264
264
  }
265
265
 
266
266
  /* Weight is the only other axis. A terminal has one type size, so emphasis has
267
- to be carried by weight and colour — never by a larger glyph. */
267
+ to be carried by weight and colour - never by a larger glyph. */
268
268
  [data-terminal] [data-weight='bold'] {
269
269
  font-weight: 600;
270
270
  }
271
271
 
272
- /* The scroller. Its horizontal padding IS `--term-bleed` — that is the whole
273
- contract a full-bleed band relies on — and its vertical padding is a line, so
272
+ /* The scroller. Its horizontal padding IS `--term-bleed` - that is the whole
273
+ contract a full-bleed band relies on - and its vertical padding is a line, so
274
274
  the first row starts one blank line down like every other block. */
275
275
  [data-terminal].term-transcript {
276
276
  padding-inline: var(--term-bleed, 0px);
@@ -280,7 +280,7 @@
280
280
  /* ── Interaction ─────────────────────────────────────────────────────────────
281
281
  *
282
282
  * A terminal has no buttons, but a transcript does have things you can open. The
283
- * affordance is the hover fill on the row itself — nothing gains a border, a
283
+ * affordance is the hover fill on the row itself - nothing gains a border, a
284
284
  * radius or a focus box that would take it off the grid.
285
285
  */
286
286
  [data-terminal] .term-press {
@@ -299,7 +299,7 @@
299
299
  this is in `press.tsx`: the click that ends such a drag is refused. */
300
300
  user-select: text;
301
301
  }
302
- /* The hover fill lives with the other affordances below — it is one, and it is
302
+ /* The hover fill lives with the other affordances below - it is one, and it is
303
303
  switchable for the same reason. */
304
304
  [data-terminal] .term-press:focus-visible {
305
305
  outline: none;
@@ -307,9 +307,9 @@
307
307
 
308
308
  /* ── Affordances ─────────────────────────────────────────────────────────────
309
309
  *
310
- * What a real terminal cannot do. Every one of these costs **no layout** — a
310
+ * What a real terminal cannot do. Every one of these costs **no layout** - a
311
311
  * fill is a background, an action is an absolutely positioned overlay one line
312
- * tall — so switching them off leaves every glyph on the cell it was already on.
312
+ * tall - so switching them off leaves every glyph on the cell it was already on.
313
313
  * See `components/terminal/affordances.tsx`.
314
314
  */
315
315
 
@@ -326,7 +326,7 @@
326
326
  /* An opened block keeps the fill the pointer gave it, for the whole of what it
327
327
  opened. Eighty lines of output pushes its own first line off the top of the
328
328
  screen, and without a wash there is nothing to say where the thing you
329
- expanded began — or that these rows are one block at all. Full-bleed, like
329
+ expanded began - or that these rows are one block at all. Full-bleed, like
330
330
  every other wash; the hover fill inside it reaches the same edge, so the two
331
331
  stack without a step. (The other half is `useRevealOnOpen`, which brings that
332
332
  first line back when it has gone above the fold.) */
@@ -339,7 +339,7 @@
339
339
  /* Another frame of reference: the rows a subagent produced, inside the `Task`
340
340
  that spawned them.
341
341
 
342
- Two cells of indent, whole — the theme's rule is that horizontal measures are
342
+ Two cells of indent, whole - the theme's rule is that horizontal measures are
343
343
  `ch`, and these rows carry markers in a gutter that has to keep landing on the
344
344
  grid the rows above it use. The rule itself is drawn *inside* that padding
345
345
  with an inset shadow rather than as a border, which is what makes the 2ch
@@ -349,7 +349,7 @@
349
349
  `--term-faint` rather than the cards `--border` token, and that is the whole
350
350
  reason this class exists: the nested block sits on the *open* block's wash,
351
351
  and a border colour tuned for the base ground resolved to rgb(31,31,31) on it
352
- — a rule nobody could see. The same trap `row-hover` is alpha to avoid. */
352
+ - a rule nobody could see. The same trap `row-hover` is alpha to avoid. */
353
353
  [data-terminal] .term-nested {
354
354
  padding-left: 2ch;
355
355
  box-shadow: inset 1px 0 0 var(--term-faint);
@@ -396,14 +396,14 @@
396
396
  color: var(--term-fg);
397
397
  }
398
398
 
399
- /* What you typed, on a band of its own — the CLI's own treatment. */
399
+ /* What you typed, on a band of its own - the CLI's own treatment. */
400
400
  [data-terminal] .term-user {
401
401
  background: var(--term-user-bg);
402
402
  margin-inline: calc(-1 * var(--term-bleed, 0px));
403
403
  padding-inline: var(--term-bleed, 0px);
404
404
  /* Declared, never inherited. This band is the one transcript block that is
405
405
  neither a `.term-press` nor plain flow text, so without saying so it takes
406
- whatever the host gives it — which differs between the browser and the VS
406
+ whatever the host gives it - which differs between the browser and the VS
407
407
  Code webview, and a block that cannot host a selection endpoint does not
408
408
  merely refuse the drag: the selection jumps to the next valid position
409
409
  above it, swallowing everything in between. */
@@ -416,10 +416,25 @@
416
416
  text-underline-offset: 2px;
417
417
  }
418
418
 
419
+ /* A link to a file opens through the host rather than the address bar, so it is a button.
420
+ * Outside `[data-terminal]` too: a cards transcript renders the same markdown. */
421
+ button.term-link {
422
+ appearance: none;
423
+ background: none;
424
+ border: 0;
425
+ padding: 0;
426
+ font: inherit;
427
+ color: inherit;
428
+ cursor: pointer;
429
+ text-align: inherit;
430
+ text-decoration: underline;
431
+ text-underline-offset: 2px;
432
+ }
433
+
419
434
  /* A file path, while the modifier that would open it is held.
420
435
  *
421
436
  * Only then, and that is the point: a path *is* clickable but only under
422
- * Cmd/Ctrl, so it should only look clickable then — an always-underlined path
437
+ * Cmd/Ctrl, so it should only look clickable then - an always-underlined path
423
438
  * promises a plain click that does nothing. Applied by `usePathLinks` using the
424
439
  * same test the click itself makes, so nothing lights up that a click ignores.
425
440
  * Outside `[data-terminal]` too, since a cards transcript names files as well. */
@@ -430,7 +445,7 @@
430
445
  }
431
446
 
432
447
  /* A status-line reading that leads somewhere. Inline and unstyled until hovered
433
- — the line is meant to be quiet, and five underlined segments would make it
448
+ - the line is meant to be quiet, and five underlined segments would make it
434
449
  the loudest row on screen. */
435
450
  [data-terminal] .term-reading {
436
451
  font: inherit;
@@ -452,7 +467,7 @@
452
467
  * row is a fill, and the only "control" is a row you can press a number on.
453
468
  */
454
469
 
455
- /* One line tall with the stroke through its middle, full-bleed — the boundary
470
+ /* One line tall with the stroke through its middle, full-bleed - the boundary
456
471
  between the run and the decision. */
457
472
  [data-terminal] .term-rule-row {
458
473
  height: var(--term-line);
@@ -474,7 +489,7 @@
474
489
  }
475
490
 
476
491
  /* An option's expanded detail (a preview, a reason field) lines up with the
477
- option's own text — one gutter in, so it reads as belonging to that row. */
492
+ option's own text - one gutter in, so it reads as belonging to that row. */
478
493
  [data-terminal] .term-detail {
479
494
  padding-left: 5ch;
480
495
  }
@@ -511,7 +526,7 @@
511
526
  color: var(--bg, #000);
512
527
  }
513
528
 
514
- /* The bare text field — a caret and a rule, no box. */
529
+ /* The bare text field - a caret and a rule, no box. */
515
530
  [data-terminal] .term-input {
516
531
  width: 100%;
517
532
  min-width: 0;
@@ -524,7 +539,7 @@
524
539
  /* The terminal's own caret: a filled cell, not a hairline. It is the last
525
540
  thing in the theme still drawn the way the web draws it, and it is the one
526
541
  the eye is always on. `caret-animation: manual` hands the blink to the
527
- author and we then decline to animate it, so the block sits steady — a
542
+ author and we then decline to animate it, so the block sits steady - a
528
543
  blinking block is a strobe at this size, where a blinking hairline was
529
544
  merely a caret. Both are ignored where unsupported, which is exactly the
530
545
  right failure: a thin blinking caret is a plain caret, not a broken one. */
@@ -536,7 +551,7 @@
536
551
  no padding and `line-height` is `--term-line`, so the content box is always a
537
552
  whole number of lines and the rule under it lands on a cell boundary. The cap is
538
553
  lines too, not a pixel height. Where `field-sizing` is unsupported this is a
539
- one-line box that scrolls — the old single-line behaviour, minus the sideways
554
+ one-line box that scrolls - the old single-line behaviour, minus the sideways
540
555
  crawl, so the degradation costs nothing that was there before. */
541
556
  [data-terminal] textarea.term-input {
542
557
  display: block;
@@ -556,8 +571,8 @@
556
571
  *
557
572
  * The pinned prompt is the prompt's **first line**, and the pin is the
558
573
  * browser's, not script's. `TranscriptRows` renders each prompt inside a lane
559
- * spanning its turn; a one-line `sticky` **head** — the same row rendered
560
- * again, clipped by height — leads the lane with its flow footprint cancelled
574
+ * spanning its turn; a one-line `sticky` **head** - the same row rendered
575
+ * again, clipped by height - leads the lane with its flow footprint cancelled
561
576
  * (the negative bottom margin), so in flow it lies exactly over the real
562
577
  * row's first line and is invisible. The compositor pins it (no per-scroll
563
578
  * JS, no frame lag) and the lane's bottom edge pushes it off when the next
@@ -567,18 +582,18 @@
567
582
  * The head's visible line docks flush to the top edge of the scroller. Its
568
583
  * inter-row gap is padding on the head (matching the row it overlays), so a
569
584
  * gap-carrying head is one line taller and pins one line negative: the
570
- * padding parks above the viewport edge and the line starts at zero — which
585
+ * padding parks above the viewport edge and the line starts at zero - which
571
586
  * is also what makes the pin engage exactly when the line itself reaches the
572
587
  * top, not a line early.
573
588
  *
574
589
  * The lane spans the whole turn and the head is an overlay, so both are
575
- * transparent to the pointer — the rows it covers own interaction, and the
576
- * head is `aria-hidden` in the markup. The z-index is on the lane —
590
+ * transparent to the pointer - the rows it covers own interaction, and the
591
+ * head is `aria-hidden` in the markup. The z-index is on the lane -
577
592
  * every row wrapper is its own stacking context (they are transformed), so
578
593
  * the head's own z-index could never beat a later sibling.
579
594
  *
580
595
  * `--bg` is the panel's own ground, the same token the hover-revealed row
581
- * actions cover themselves with — the user band is translucent by design, so
596
+ * actions cover themselves with - the user band is translucent by design, so
582
597
  * without a ground the rows passing beneath would show straight through the
583
598
  * pinned head. Opaque always, which is invisible in flow: the ground behind
584
599
  * it is the same `--bg`, and the row it covers is its own content.
@@ -591,14 +606,14 @@
591
606
  /* The prompt itself is *inside* the lane, not beneath it, so the lane's `none`
592
607
  reached it too and made a user message the one block in the transcript the
593
608
  pointer could not touch: a drag started on it landed on the lane instead and
594
- selected nothing. Only the real row is given back — the head overlay above
609
+ selected nothing. Only the real row is given back - the head overlay above
595
610
  stays transparent, which is the whole point of the lane. */
596
611
  [data-terminal] [data-sticky-lane] > [data-index] {
597
612
  pointer-events: auto;
598
613
  }
599
614
 
600
615
  /* The head's own strip: the full lane, out of flow, so the head needs no
601
- footprint-cancelling margin — sticky confinement clamps the margin box, and
616
+ footprint-cancelling margin - sticky confinement clamps the margin box, and
602
617
  a negative margin shrinks it to nothing, letting the head overshoot the
603
618
  lane's end by its own height (two pinned prompts on screen at once). */
604
619
  [data-terminal] [data-sticky-lane] > [data-sticky-headlane] {
@@ -609,7 +624,7 @@
609
624
 
610
625
  /* Hidden until actually stuck (`data-stuck`, set by the lane's sentinel
611
626
  observer): an overlay visible in flow sits on the real row's first line and
612
- swallows its selection highlight — the line reads as unselectable. */
627
+ swallows its selection highlight - the line reads as unselectable. */
613
628
  [data-terminal] [data-sticky-headlane] > [data-sticky-head] {
614
629
  position: sticky;
615
630
  top: 0;
@@ -632,19 +647,19 @@
632
647
  /* ── Composer ────────────────────────────────────────────────────────────────
633
648
  *
634
649
  * The foot of the panel, and a *row* like every other: `>` in the gutter cell,
635
- * the caret on the body column. That is the whole design — a typed line and a
650
+ * the caret on the body column. That is the whole design - a typed line and a
636
651
  * rendered one start on the same character, which no chat composer does and
637
652
  * every terminal does for free.
638
653
  *
639
654
  * Docked rather than floating: no radius, no shadow, and a rule along the top
640
655
  * *and* the bottom, both turning accent while anything inside has focus. Those
641
656
  * two rules are the CLI's own frame for its prompt, and they are the only
642
- * affordance — which is what the transcript above (no boxes anywhere) asks for.
657
+ * affordance - which is what the transcript above (no boxes anywhere) asks for.
643
658
  * They also do the work a box would: the field reads as its own strip of the
644
659
  * panel rather than as the last row of the transcript, without a border on the
645
660
  * sides that would take the `❯` off the column the rows start on.
646
661
  *
647
- * The bottom rule is why the status bar under it draws none of its own — see
662
+ * The bottom rule is why the status bar under it draws none of its own - see
648
663
  * `StatusBar.tsx`. Two adjacent 1px rules is a 2px rule with a seam in it.
649
664
  */
650
665
  [data-terminal].term-composer {
@@ -654,7 +669,7 @@
654
669
  }
655
670
  /* The bar spans the panel; its contents sit in the transcript's own column, so
656
671
  the `>` lands on the column the rows above it start on. Two elements because
657
- those are two different widths — a rule that stopped at the content column
672
+ those are two different widths - a rule that stopped at the content column
658
673
  would read as a card, which is the one thing this theme has none of. */
659
674
  [data-terminal] .term-composer-body {
660
675
  margin-inline: auto;
@@ -663,7 +678,7 @@
663
678
  padding-inline: var(--term-bleed, 0px);
664
679
  /* Air on each side of the prompt, and a plain pixel value rather than a
665
680
  fraction of `--term-line`: this is chrome between two rules, not a
666
- transcript row, and the whole-multiple rule governs rows — a computed
681
+ transcript row, and the whole-multiple rule governs rows - a computed
667
682
  half-line would be exactly the fractional metric that rule exists to
668
683
  forbid. */
669
684
  padding-block: 8px;
@@ -674,7 +689,7 @@
674
689
  }
675
690
 
676
691
  /* Shell mode. The frame and the gutter glyph move together and both go magenta, because the thing
677
- the mode changes is where the text goes — a subtle cue would be the wrong kind of quiet. Listed
692
+ the mode changes is where the text goes - a subtle cue would be the wrong kind of quiet. Listed
678
693
  after the focus rule so it wins while the field is focused, which is the whole time it is on. */
679
694
  [data-terminal].term-composer-shell,
680
695
  [data-terminal].term-composer-shell:focus-within {
@@ -684,7 +699,7 @@
684
699
  color: var(--term-dim);
685
700
  }
686
701
 
687
- /* The contentEditable field. `font: inherit` is what makes it the cell — the
702
+ /* The contentEditable field. `font: inherit` is what makes it the cell - the
688
703
  surface above already carries the size and the line height, and anything that
689
704
  restates them here can disagree with the rows. */
690
705
  [data-terminal] .term-composer-field {
@@ -702,7 +717,7 @@
702
717
  * badge outside its top-right corner, over a strip with its own bottom rule.
703
718
  * None of that survives here: this theme has no rounded corners and no badges
704
719
  * floating off an edge, and the strip sits *inside* the composer's frame, which
705
- * already draws the only rule there is — a second one directly above the field
720
+ * already draws the only rule there is - a second one directly above the field
706
721
  * reads as the box this theme exists not to have.
707
722
  *
708
723
  * So the thumbnail is a plain 1px cell, and the remove control is tucked into
@@ -711,7 +726,7 @@
711
726
  * puts it just inside the thumbnail's border instead of straddling it.
712
727
  *
713
728
  * The strip's only spacing is the 8px the composer already uses above and below
714
- * its prompt — a plain pixel value, and deliberately, for the reason given
729
+ * its prompt - a plain pixel value, and deliberately, for the reason given
715
730
  * there: this is chrome between rules, not a transcript row, so the
716
731
  * whole-multiple-of-`--term-line` rule does not govern it. No side padding: the
717
732
  * strip is already inside the body column, so padding here would step it in
@@ -724,7 +739,7 @@
724
739
  }
725
740
  /* A refused upload, in the terminal's own red. This needs to be said here
726
741
  rather than left to the card form's `border-danger/50`: the rule above sets
727
- the `border` shorthand, colour included, and outranks a utility class — so
742
+ the `border` shorthand, colour included, and outranks a utility class - so
728
743
  without this the one state that must stand out is the one that looks
729
744
  ordinary. */
730
745
  [data-terminal] .term-attachment[data-failed] {
@@ -739,7 +754,7 @@
739
754
 
740
755
  /* A composer action as a character: one cell wide, one line tall, so pressing
741
756
  it costs no width and removing it would move nothing. Dim until you reach for
742
- it — the same restraint the row actions show. */
757
+ it - the same restraint the row actions show. */
743
758
  [data-terminal] .term-glyph {
744
759
  flex: none;
745
760
  width: var(--term-cell);
@@ -755,8 +770,8 @@
755
770
  cursor: pointer;
756
771
  }
757
772
  /* A glyph standing in the **gutter cell** is a marker, so it aligns like one.
758
- The cell is 2ch and a character is 1ch, so `text-align: center` — right for a
759
- standalone control at the row's trailing edge — lands the glyph half a cell
773
+ The cell is 2ch and a character is 1ch, so `text-align: center` - right for a
774
+ standalone control at the row's trailing edge - lands the glyph half a cell
760
775
  (≈3.9px at 13px/JetBrains Mono) to the right of the column every `❯` and `●`
761
776
  in the transcript starts on. `start` is what `.term-gutter` uses, and on this
762
777
  grid "nearly the same column" is just the wrong column. */
@@ -784,7 +799,7 @@
784
799
  * An added or removed line is a **band**, not coloured text: the wash runs the
785
800
  * full width of the screen, the way it does in a terminal, so the shape of a
786
801
  * change is readable from the scrollbar without reading a word of it. The line
787
- * numbers ride inside the wash — they are part of the changed line.
802
+ * numbers ride inside the wash - they are part of the changed line.
788
803
  */
789
804
  /* A diff belongs to the call above it, so it steps in one cell like any other
790
805
  tool output. */
@@ -813,7 +828,7 @@
813
828
  [data-terminal] .term-diff .term-row[data-diff='context'] {
814
829
  color: var(--term-diff-fg);
815
830
  }
816
- /* The number column is dimmer than the line it belongs to in every case — it is
831
+ /* The number column is dimmer than the line it belongs to in every case - it is
817
832
  an address, not content. `.term-gutter`'s own colour would flatten all three
818
833
  to one grey, so each state dims its own. */
819
834
  [data-terminal] .term-diff .term-row > .term-gutter {
@@ -829,13 +844,13 @@
829
844
  /* ── Markdown ────────────────────────────────────────────────────────────────
830
845
  *
831
846
  * The element map is in `components/terminal/markdown.tsx`; this is the little
832
- * CSS it needs. Nothing here is an override — the renderer is told what to emit,
847
+ * CSS it needs. Nothing here is an override - the renderer is told what to emit,
833
848
  * so there is no default to undo and no `!important` anywhere in the theme.
834
849
  */
835
850
 
836
851
  /* react-markdown emits the source's newlines as text nodes between blocks. The
837
852
  surrounding rows are `pre-wrap` (a plain line of output must keep its own
838
- spacing), which would render every one of those as a visible blank line — so
853
+ spacing), which would render every one of those as a visible blank line - so
839
854
  markdown bodies opt back out, and only a fenced block opts back in. */
840
855
  .term-md {
841
856
  white-space: normal;
@@ -844,7 +859,7 @@
844
859
  /* And it has to reach `.term-body` explicitly, because a list item *is* a Row
845
860
  and Rows carry `pre-wrap` (a plain line of tool output must keep its own
846
861
  spacing). Inherited, that turns every newline react-markdown emits between an
847
- item's block children into a visible empty line — a nested list drew k+2
862
+ item's block children into a visible empty line - a nested list drew k+2
848
863
  phantom blanks, so a two-line nested item was 72px of 18px rows. Models nest
849
864
  lists constantly, and the grid audit cannot see it: the phantoms are whole
850
865
  lines, so every row still lands on the grid. Fenced code opts back in for
@@ -856,7 +871,7 @@
856
871
 
857
872
  /* The one vertical rule: consecutive blocks are one blank line apart. A margin
858
873
  rather than an inserted row, because this row list belongs to the markdown
859
- renderer — but it is exactly one line, so the grid holds. */
874
+ renderer - but it is exactly one line, so the grid holds. */
860
875
  .term-md .term-block + .term-block {
861
876
  margin-top: var(--term-line);
862
877
  }
@@ -889,7 +904,7 @@
889
904
  background: linear-gradient(var(--term-faint), var(--term-faint)) left / 1px 100% no-repeat;
890
905
  }
891
906
 
892
- /* A horizontal rule is one line tall with the rule through its middle — the row
907
+ /* A horizontal rule is one line tall with the rule through its middle - the row
893
908
  it occupies is a whole line either way. */
894
909
  .term-md .term-rule {
895
910
  height: var(--term-line);
@@ -921,7 +936,7 @@
921
936
  border-collapse: collapse;
922
937
  /* `max-content` and **no** `max-width`. A cap of `100%` looks like the
923
938
  defensive choice and is the opposite: it clamps a wide table below its
924
- natural width, so the cells wrap and a 14-row table becomes 19 lines tall —
939
+ natural width, so the cells wrap and a 14-row table becomes 19 lines tall -
925
940
  which breaks the theme's premise (a row is a line) and with it the height
926
941
  calculator, whose table model counts rows. It also silently disables the
927
942
  wrapper's own `overflow-x`, since a table that shrinks never overflows.
@@ -943,15 +958,15 @@
943
958
  /* ── Scrubber ────────────────────────────────────────────────────────────────
944
959
  *
945
960
  * The overview ruler (`components/terminal/scrubber.tsx`): a 12px rail of marks
946
- * over the scroller's right edge — VS Code's strip beside the minimap, with
961
+ * over the scroller's right edge - VS Code's strip beside the minimap, with
947
962
  * this transcript's semantics. It is *chrome*, not rows: absolutely positioned
948
963
  * over the scroll container so the transcript's grid is untouched, and drawn
949
964
  * entirely from --term-* tokens so it follows the theme wherever the panel is
950
965
  * embedded. Its width is the one deliberate exception to the theme's `ch` rule
951
- * — see the note on `.term-scrubber` below.
966
+ * - see the note on `.term-scrubber` below.
952
967
  *
953
968
  * Interactivity is an affordance. Without `data-interactive` the rail is
954
- * passive paint with pointer events off — and the native scrollbar, which
969
+ * passive paint with pointer events off - and the native scrollbar, which
955
970
  * stays in that mode, keeps working straight through it.
956
971
  */
957
972
  [data-terminal].term-scrubber {
@@ -977,7 +992,7 @@
977
992
  touch-action: none;
978
993
  }
979
994
 
980
- /* The viewport band — where you are: a faint wash with a hairline frame,
995
+ /* The viewport band - where you are: a faint wash with a hairline frame,
981
996
  floated over the marks (z-index) so the framed region reads as a window
982
997
  onto the rail rather than a stripe behind it. `--term-faint` at 25% rather
983
998
  than a literal, so the light palette re-tints it. */
@@ -992,7 +1007,7 @@
992
1007
  }
993
1008
 
994
1009
  /* There is no separate cursor line. The band above answers "where am I" on its
995
- own — it is outlined, so its top edge already reads as an edge — and a 2px
1010
+ own - it is outlined, so its top edge already reads as an edge - and a 2px
996
1011
  blue rule laid over it was a second indicator of the same fact, in the
997
1012
  loudest colour the rail has. */
998
1013
 
@@ -1007,7 +1022,7 @@
1007
1022
  outline: 1px solid color-mix(in srgb, var(--term-bright) 40%, transparent);
1008
1023
  }
1009
1024
 
1010
- /* Two lanes of 8px — what you asked, and what came back — and 'f', the
1025
+ /* Two lanes of 8px - what you asked, and what came back - and 'f', the
1011
1026
  full-width lane for annotations *about* the run (an error, an approval, a
1012
1027
  bookmark, the catch-up seam) rather than steps through it. Pixels, not
1013
1028
  percentages: the whole point of the 16px rail is that a lane is a reliable
@@ -1026,7 +1041,7 @@
1026
1041
  width: 12px;
1027
1042
  }
1028
1043
 
1029
- /* Lane marks are two-tone: the first 2px — the mark's own anchor — at full
1044
+ /* Lane marks are two-tone: the first 2px - the mark's own anchor - at full
1030
1045
  strength, the rest of the row's extent a 25% tail, so a long answer reads
1031
1046
  as long without a tall solid bar shouting over the rail. A minimum-height
1032
1047
  mark shows only the solid segment, so short and long marks need no separate
@@ -1038,7 +1053,7 @@
1038
1053
  }
1039
1054
  /* A sub-agent's stretch of the transcript, in the input lane: green, because
1040
1055
  every other colour on this rail is already spoken for and none of them means
1041
- "somebody else's working" — blue is you, white is the answer, red is an
1056
+ "somebody else's working" - blue is you, white is the answer, red is an
1042
1057
  alarm, magenta is your bookmark, yellow is the session waiting on you. Drawn
1043
1058
  as an extent (2px head, 25% tail) like the two marks it shares its geometry
1044
1059
  with: collapsed it is a tick, expanded it is the band the sub-agent covers. */
@@ -1054,13 +1069,13 @@
1054
1069
  .term-scrubber .term-scrub-mark[data-kind='error'] {
1055
1070
  background: var(--term-red);
1056
1071
  }
1057
- /* A failed tool call is an alarm, so it is solid like the rest of them — in the
1058
- response lane, since it is something the run produced — but at 55%, which is the one thing keeping the rail readable. A session
1072
+ /* A failed tool call is an alarm, so it is solid like the rest of them - in the
1073
+ response lane, since it is something the run produced - but at 55%, which is the one thing keeping the rail readable. A session
1059
1074
  error is rare and a turn failure rarer; a tool that failed and was recovered
1060
1075
  from is routine (a grep that matched nothing, a build fixed on the second go),
1061
1076
  and at full strength a normal working session paints the rail solid red and
1062
1077
  the two errors that actually ended something stop standing out. Dimmer reads
1063
- as the same class of thing, one rank quieter — which is exactly its rank in
1078
+ as the same class of thing, one rank quieter - which is exactly its rank in
1064
1079
  LOUDNESS. */
1065
1080
  .term-scrubber .term-scrub-mark[data-kind='toolFailed'] {
1066
1081
  background: color-mix(in srgb, var(--term-red) 55%, transparent);
@@ -1087,8 +1102,8 @@
1087
1102
  }
1088
1103
  }
1089
1104
 
1090
- /* The peek. Rendered from transcript state — the row it describes is usually
1091
- unmounted — and framed the theme's way: strokes as backgrounds, no border.
1105
+ /* The peek. Rendered from transcript state - the row it describes is usually
1106
+ unmounted - and framed the theme's way: strokes as backgrounds, no border.
1092
1107
  pointer-events: none, so it can never trap the pointer that revealed it. */
1093
1108
  .term-scrubber .term-scrub-peek {
1094
1109
  position: absolute;
@@ -1124,7 +1139,7 @@
1124
1139
 
1125
1140
  /* While an interactive scrubber is mounted it IS the scrollbar; the transcript
1126
1141
  stamps this attribute on the scroll element. A passive rail leaves the
1127
- native scrollbar alone — pointer-events: none above is what keeps that
1142
+ native scrollbar alone - pointer-events: none above is what keeps that
1128
1143
  scrollbar reachable through the paint. */
1129
1144
  [data-term-scrubber-host] {
1130
1145
  scrollbar-width: none;
@@ -1138,7 +1153,7 @@
1138
1153
  * A picture a tool returned, drawn in a box of whole lines (`IMAGE_BOX_LINES`,
1139
1154
  * set inline by `TerminalImage` so the constant has one spelling across the
1140
1155
  * renderer and the height calculator). The height is on the element and nothing
1141
- * here may add to it: no margin, no border, no padding — a box that is not
1156
+ * here may add to it: no margin, no border, no padding - a box that is not
1142
1157
  * exactly N lines is a row the virtualizer was told the wrong size for.
1143
1158
  *
1144
1159
  * `object-fit: contain` with a top-left origin rather than a centred one: the