@workerdeck/ui 0.15.0 → 0.17.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 +60 -0
  2. package/build/{SessionPanel-J2U8v88q.d.mts → SessionPanel-CnNYEX80.d.mts} +170 -21
  3. package/build/{SessionPanel-DI1NO4l8.mjs → SessionPanel-DMPhsNlW.mjs} +4759 -1483
  4. package/build/SessionPanel-DMPhsNlW.mjs.map +1 -0
  5. package/build/{format-ljc3lKpA.d.mts → format-DfI_je9S.d.mts} +1 -1
  6. package/build/format.d.mts +39 -4
  7. package/build/format.mjs +2 -118
  8. package/build/index.d.mts +493 -45
  9. package/build/index.mjs +343 -17
  10. package/build/index.mjs.map +1 -1
  11. package/build/status-Ydzi7n6j.mjs +143 -0
  12. package/build/status-Ydzi7n6j.mjs.map +1 -0
  13. package/build/workspace.d.mts +9 -1
  14. package/build/workspace.mjs +108 -5
  15. package/build/workspace.mjs.map +1 -1
  16. package/package.json +16 -7
  17. package/src/components/agent/Composer.tsx +189 -89
  18. package/src/components/agent/Conversation.tsx +12 -12
  19. package/src/components/agent/FileCard.tsx +0 -26
  20. package/src/components/agent/FileTree.tsx +9 -8
  21. package/src/components/agent/Loader.tsx +22 -72
  22. package/src/components/agent/Message.tsx +11 -46
  23. package/src/components/agent/PermissionPrompt.tsx +0 -92
  24. package/src/components/agent/ProjectIcon.tsx +119 -0
  25. package/src/components/agent/QuestionPrompt.tsx +0 -122
  26. package/src/components/agent/Reasoning.tsx +5 -19
  27. package/src/components/agent/Response.tsx +1 -132
  28. package/src/components/agent/SessionBrowser.tsx +84 -3
  29. package/src/components/agent/SessionPanel.tsx +249 -28
  30. package/src/components/agent/SessionWorkspace.tsx +29 -0
  31. package/src/components/agent/StatusBar.tsx +20 -4
  32. package/src/components/agent/ToolCallCard.tsx +85 -112
  33. package/src/components/agent/Transcript.tsx +780 -203
  34. package/src/components/agent/UsageDialog.tsx +20 -106
  35. package/src/components/agent/UsageMeters.tsx +133 -0
  36. package/src/components/agent/pulse.tsx +3 -2
  37. package/src/components/agent/tool-result-fetch.tsx +36 -0
  38. package/src/components/agent/tool-result-image.tsx +209 -0
  39. package/src/components/agent/transcript-rows.ts +173 -0
  40. package/src/components/agent/transcript-variant.tsx +29 -51
  41. package/src/components/agent/use-height-epoch.ts +60 -0
  42. package/src/components/agent/use-path-links.ts +147 -0
  43. package/src/components/agent/use-transcript-jumps.ts +190 -0
  44. package/src/components/prompt-area/cursor-helpers.ts +65 -0
  45. package/src/components/prompt-area/use-prompt-area.ts +16 -10
  46. package/src/components/terminal/PermissionPrompt.tsx +119 -0
  47. package/src/components/terminal/QuestionPrompt.tsx +322 -0
  48. package/src/components/terminal/StatusLine.tsx +159 -0
  49. package/src/components/terminal/TerminalTranscript.tsx +225 -0
  50. package/src/components/terminal/affordances.tsx +118 -0
  51. package/src/components/terminal/blocks.ts +232 -0
  52. package/src/components/terminal/diff.tsx +130 -0
  53. package/src/components/terminal/height.ts +770 -0
  54. package/src/components/terminal/image-box.ts +53 -0
  55. package/src/components/terminal/items.tsx +486 -0
  56. package/src/components/terminal/markdown.tsx +191 -0
  57. package/src/components/terminal/press.tsx +120 -0
  58. package/src/components/terminal/prompt.tsx +343 -0
  59. package/src/components/terminal/result-preview.ts +86 -0
  60. package/src/components/terminal/row.tsx +132 -0
  61. package/src/components/terminal/scrubber.tsx +784 -0
  62. package/src/components/terminal/surface.tsx +80 -0
  63. package/src/components/terminal/tool-run.ts +224 -0
  64. package/src/index.ts +36 -1
  65. package/src/lib/status.ts +59 -3
  66. package/src/lib/tool-icon.ts +14 -0
  67. package/src/styles/terminal.css +1081 -0
  68. package/src/styles/theme.css +41 -0
  69. package/build/SessionPanel-DI1NO4l8.mjs.map +0 -1
  70. package/build/format.mjs.map +0 -1
  71. package/src/components/agent/line-prompt.tsx +0 -249
@@ -0,0 +1,86 @@
1
+ /**
2
+ * How much of a tool result a collapsed row shows, and what it says it hid.
3
+ *
4
+ * Its own module, and pure, because **two** consumers must agree on it to the
5
+ * character: `items.tsx` draws these rows, and `height.ts` predicts their pixel
6
+ * height for the virtualizer's `estimateSize` without a DOM. The budget used to
7
+ * be a private constant in `items.tsx` restated as a copy in `height.ts` with a
8
+ * comment admitting the drift risk — this is that comment's fix.
9
+ *
10
+ * **Two budgets, not one.** Lines alone was the old rule and it has an exact
11
+ * blind spot: a minified JSON reply — which is every MCP tool's reply — is ONE
12
+ * line, so a four-line slice kept all thirty thousand characters of it and the
13
+ * row wrapped to a screenful. `hidden` was computed as `lines.length -
14
+ * shown.length`, so it came out zero and the row did not even offer the "+N"
15
+ * affordance: the whole blob was simply the transcript now. Characters alone
16
+ * would be wrong the other way, cutting an ordinary short-line result mid-way
17
+ * for no reason. So both apply, and a *first* line longer than the budget is
18
+ * truncated rather than shown whole — a row has to show something or it opens
19
+ * onto nothing.
20
+ */
21
+
22
+ /** At most this many lines, however short they are. */
23
+ const PREVIEW_LINES = 4
24
+ /**
25
+ * …and at most this many characters, however few lines they are. Four lines'
26
+ * worth at any realistic terminal width — the row is indented six cells, so a
27
+ * 100ch panel fits ~94 per line — which keeps the budget honest whether the
28
+ * result arrives as four lines or as one long one.
29
+ */
30
+ const PREVIEW_CHARS = 400
31
+
32
+ export type CollapsedResult = {
33
+ /** The lines to draw. The last may be truncated (it ends in `…`). */
34
+ shown: string[]
35
+ /**
36
+ * The trailing "there is more" row, already spelled. The *string* rather than
37
+ * a count, because `height.ts` wraps this exact text to size the row, and two
38
+ * spellings would be two different heights.
39
+ */
40
+ more?: string
41
+ }
42
+
43
+ /**
44
+ * Reported in characters when the truncation happened *inside* a line and in
45
+ * lines otherwise — a one-line JSON blob has no hidden lines to count, and
46
+ * "+0 lines" under a visibly cut-off row is worse than saying nothing.
47
+ */
48
+ /**
49
+ * `totalChars` is the **untruncated** length when the replay delivered only a
50
+ * head (protocol's `ToolResultBlock.total_chars`). Passing it is not cosmetic:
51
+ * computed from the head this row would say "… +7,600 chars" where the truth is
52
+ * 641,003, and the wrong string is a *different pixel height* — which is exactly
53
+ * the drift this module exists to prevent, since `height.ts` sizes the row by
54
+ * wrapping this same text. Omitted for a whole result, where the lines are the
55
+ * whole truth.
56
+ */
57
+ export function collapsedResult(lines: string[], totalChars?: number): CollapsedResult {
58
+ const shown: string[] = []
59
+ let chars = 0
60
+ let cut = false
61
+
62
+ for (const line of lines.slice(0, PREVIEW_LINES)) {
63
+ if (shown.length === 0 && line.length > PREVIEW_CHARS) {
64
+ shown.push(`${line.slice(0, PREVIEW_CHARS)}…`)
65
+ chars = PREVIEW_CHARS
66
+ cut = true
67
+ break
68
+ }
69
+ if (shown.length > 0 && chars + line.length > PREVIEW_CHARS) break
70
+ shown.push(line)
71
+ chars += line.length + 1
72
+ }
73
+
74
+ // `join` because the newlines are part of what is not being shown — and
75
+ // `totalChars` wins when it exists, because the lines in hand are then a head
76
+ // rather than the result.
77
+ const held = lines.join('\n').length
78
+ const total = totalChars ?? held
79
+ if (cut) return { shown, more: `… +${(total - chars).toLocaleString()} chars` }
80
+ // A truncated result always has more, even when its head happened to fit the
81
+ // line budget: the row must never claim to be showing everything.
82
+ if (totalChars !== undefined && total > held)
83
+ return { shown, more: `… +${(total - chars).toLocaleString()} chars` }
84
+ const hidden = lines.length - shown.length
85
+ return { shown, more: hidden > 0 ? `… +${hidden} line${hidden === 1 ? '' : 's'}` : undefined }
86
+ }
@@ -0,0 +1,132 @@
1
+ import type { CSSProperties, HTMLAttributes, ReactNode } from 'react'
2
+ import { cn } from '../../lib/utils.ts'
3
+
4
+ /**
5
+ * The primitives every terminal row is built from.
6
+ *
7
+ * There are three, and that is the whole vocabulary: a {@link Row} (a gutter
8
+ * cell and a body cell), a {@link Blank} (one empty line), and a {@link Band} (a
9
+ * run of rows carrying a full-bleed background). Anything the theme draws — a
10
+ * message, a tool call, a diff hunk, an approval prompt — is some arrangement of
11
+ * those, which is what keeps the grid a property of the renderer rather than a
12
+ * thing each component re-derives.
13
+ *
14
+ * Geometry is in `styles/terminal.css`. These components choose a class, a
15
+ * marker and a tone; they never carry a measurement.
16
+ */
17
+
18
+ /** The palette, as a name. See the `[data-tone]` rules in `terminal.css`. */
19
+ export type Tone =
20
+ | 'fg'
21
+ | 'bright'
22
+ | 'dim'
23
+ | 'faint'
24
+ | 'mark'
25
+ | 'blue'
26
+ | 'green'
27
+ | 'red'
28
+ | 'yellow'
29
+ | 'magenta'
30
+
31
+ export interface RowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
32
+ /**
33
+ * What sits in the gutter — `●`, `⎿`, `>`, a list bullet, or nothing. Kept to
34
+ * the width of the gutter (`--term-cell`, two columns by default): a wider
35
+ * marker would push its own body off the column every other row starts on.
36
+ * Omitted, the gutter is still drawn as empty space, so an unmarked row's
37
+ * text lines up with a marked one's.
38
+ */
39
+ glyph?: ReactNode
40
+ /** The marker's colour. Defaults to dim — the marker is structure, not content. */
41
+ glyphTone?: Tone
42
+ /** The body's colour. */
43
+ tone?: Tone
44
+ bold?: boolean
45
+ /** Indent levels, one character cell each. A child row's marker then sits
46
+ * exactly under its parent row's first letter. */
47
+ indent?: 0 | 1 | 2 | 3
48
+ /**
49
+ * Gutter width in columns, when the marker needs other than two — an ordered
50
+ * list's `10.` is four, a prompt's `❯ 1.` is five, and a framed payload wants
51
+ * `0`. Changes only this row's split, so the body still starts on a whole
52
+ * column.
53
+ */
54
+ columns?: number
55
+ children?: ReactNode
56
+ }
57
+
58
+ export function Row({
59
+ glyph,
60
+ glyphTone,
61
+ tone,
62
+ bold,
63
+ indent,
64
+ columns,
65
+ className,
66
+ children,
67
+ style,
68
+ ...props
69
+ }: RowProps) {
70
+ return (
71
+ <div
72
+ className={cn('term-row', className)}
73
+ data-indent={indent ? String(indent) : undefined}
74
+ data-tone={tone}
75
+ data-weight={bold ? 'bold' : undefined}
76
+ // `!== undefined`, not truthiness: `columns={0}` is a real request for a
77
+ // gutterless row (a framed payload) and must not fall back to the default.
78
+ style={
79
+ columns === undefined
80
+ ? style
81
+ : ({ ...style, '--term-cell': `${columns}ch` } as CSSProperties)
82
+ }
83
+ {...props}>
84
+ <span className='term-gutter' data-tone={glyphTone} aria-hidden>
85
+ {glyph ?? ' '}
86
+ </span>
87
+ {/* A div, not a span: a body holds block content (a markdown message, a
88
+ band of output) as often as it holds a line of text. */}
89
+ <div className='term-body'>{children}</div>
90
+ </div>
91
+ )
92
+ }
93
+
94
+ /**
95
+ * One empty line — the theme's only vertical spacing.
96
+ *
97
+ * A terminal separates blocks with a blank line, not with padding, and saying it
98
+ * that way has a practical payoff: spacing is part of the row list, so it can be
99
+ * decided by whoever knows whether two blocks belong together (a tool call and
100
+ * its output do not get one; two assistant turns do), instead of by a margin
101
+ * rule that cannot tell them apart.
102
+ */
103
+ export function Blank() {
104
+ return <div className='term-blank' aria-hidden />
105
+ }
106
+
107
+ /**
108
+ * A run of rows under a background: a code block, a command's output, a diff
109
+ * hunk. Full-bleed — the wash reaches the scroller's edges, because in a
110
+ * terminal the line is the full width of the screen. See `--term-bleed` on
111
+ * {@link TerminalSurface}.
112
+ */
113
+ export function Band({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
114
+ return <div className={cn('term-band', className)} {...props} />
115
+ }
116
+
117
+ /** Inline colour/weight inside a row's body. */
118
+ export function Ink({
119
+ tone,
120
+ bold,
121
+ className,
122
+ ...props
123
+ }: HTMLAttributes<HTMLSpanElement> & { tone?: Tone; bold?: boolean }) {
124
+ return (
125
+ <span
126
+ data-tone={tone}
127
+ data-weight={bold ? 'bold' : undefined}
128
+ className={className}
129
+ {...props}
130
+ />
131
+ )
132
+ }