@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.
- package/README.md +60 -0
- package/build/{SessionPanel-J2U8v88q.d.mts → SessionPanel-CnNYEX80.d.mts} +170 -21
- package/build/{SessionPanel-DI1NO4l8.mjs → SessionPanel-DMPhsNlW.mjs} +4759 -1483
- package/build/SessionPanel-DMPhsNlW.mjs.map +1 -0
- package/build/{format-ljc3lKpA.d.mts → format-DfI_je9S.d.mts} +1 -1
- package/build/format.d.mts +39 -4
- package/build/format.mjs +2 -118
- package/build/index.d.mts +493 -45
- package/build/index.mjs +343 -17
- package/build/index.mjs.map +1 -1
- package/build/status-Ydzi7n6j.mjs +143 -0
- package/build/status-Ydzi7n6j.mjs.map +1 -0
- package/build/workspace.d.mts +9 -1
- package/build/workspace.mjs +108 -5
- package/build/workspace.mjs.map +1 -1
- package/package.json +16 -7
- package/src/components/agent/Composer.tsx +189 -89
- package/src/components/agent/Conversation.tsx +12 -12
- package/src/components/agent/FileCard.tsx +0 -26
- package/src/components/agent/FileTree.tsx +9 -8
- package/src/components/agent/Loader.tsx +22 -72
- package/src/components/agent/Message.tsx +11 -46
- package/src/components/agent/PermissionPrompt.tsx +0 -92
- package/src/components/agent/ProjectIcon.tsx +119 -0
- package/src/components/agent/QuestionPrompt.tsx +0 -122
- package/src/components/agent/Reasoning.tsx +5 -19
- package/src/components/agent/Response.tsx +1 -132
- package/src/components/agent/SessionBrowser.tsx +84 -3
- package/src/components/agent/SessionPanel.tsx +249 -28
- package/src/components/agent/SessionWorkspace.tsx +29 -0
- package/src/components/agent/StatusBar.tsx +20 -4
- package/src/components/agent/ToolCallCard.tsx +85 -112
- package/src/components/agent/Transcript.tsx +780 -203
- package/src/components/agent/UsageDialog.tsx +20 -106
- package/src/components/agent/UsageMeters.tsx +133 -0
- package/src/components/agent/pulse.tsx +3 -2
- package/src/components/agent/tool-result-fetch.tsx +36 -0
- package/src/components/agent/tool-result-image.tsx +209 -0
- package/src/components/agent/transcript-rows.ts +173 -0
- package/src/components/agent/transcript-variant.tsx +29 -51
- package/src/components/agent/use-height-epoch.ts +60 -0
- package/src/components/agent/use-path-links.ts +147 -0
- package/src/components/agent/use-transcript-jumps.ts +190 -0
- package/src/components/prompt-area/cursor-helpers.ts +65 -0
- package/src/components/prompt-area/use-prompt-area.ts +16 -10
- package/src/components/terminal/PermissionPrompt.tsx +119 -0
- package/src/components/terminal/QuestionPrompt.tsx +322 -0
- package/src/components/terminal/StatusLine.tsx +159 -0
- package/src/components/terminal/TerminalTranscript.tsx +225 -0
- package/src/components/terminal/affordances.tsx +118 -0
- package/src/components/terminal/blocks.ts +232 -0
- package/src/components/terminal/diff.tsx +130 -0
- package/src/components/terminal/height.ts +770 -0
- package/src/components/terminal/image-box.ts +53 -0
- package/src/components/terminal/items.tsx +486 -0
- package/src/components/terminal/markdown.tsx +191 -0
- package/src/components/terminal/press.tsx +120 -0
- package/src/components/terminal/prompt.tsx +343 -0
- package/src/components/terminal/result-preview.ts +86 -0
- package/src/components/terminal/row.tsx +132 -0
- package/src/components/terminal/scrubber.tsx +784 -0
- package/src/components/terminal/surface.tsx +80 -0
- package/src/components/terminal/tool-run.ts +224 -0
- package/src/index.ts +36 -1
- package/src/lib/status.ts +59 -3
- package/src/lib/tool-icon.ts +14 -0
- package/src/styles/terminal.css +1081 -0
- package/src/styles/theme.css +41 -0
- package/build/SessionPanel-DI1NO4l8.mjs.map +0 -1
- package/build/format.mjs.map +0 -1
- 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
|
+
}
|