@workerdeck/ui 2.15.0 → 3.1.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 +6 -5
- package/build/{SessionPanel-BVKqPs_3.d.mts → SessionPanel-BNNc8grK.d.mts} +18 -16
- package/build/index.d.mts +194 -49
- package/build/index.mjs +562 -260
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +170 -21
- package/build/{use-path-links-BgWXmfAS.mjs → use-path-links-C6Fh5zdx.mjs} +3312 -2758
- package/build/use-path-links-C6Fh5zdx.mjs.map +1 -0
- package/build/workspace.d.mts +3 -3
- package/build/workspace.mjs +3 -3
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/ApprovalPrompts.tsx +87 -0
- package/src/components/agent/Composer.tsx +25 -47
- package/src/components/agent/ContextDialog.tsx +75 -67
- package/src/components/agent/FrameStrip.tsx +74 -0
- package/src/components/agent/HostFilesDialog.tsx +3 -2
- package/src/components/agent/McpDialog.tsx +112 -78
- package/src/components/agent/PermissionPrompt.tsx +22 -17
- package/src/components/agent/QuestionPrompt.tsx +6 -41
- package/src/components/agent/Response.tsx +8 -1
- package/src/components/agent/Scrubber.tsx +6 -75
- package/src/components/agent/SessionBrowser.tsx +131 -214
- package/src/components/agent/SessionFilters.tsx +266 -0
- package/src/components/agent/SessionInfoDialog.tsx +47 -63
- package/src/components/agent/SessionItem.tsx +29 -6
- package/src/components/agent/SessionPanel.tsx +249 -498
- package/src/components/agent/SessionSteps.tsx +98 -14
- package/src/components/agent/SessionWorkspace.tsx +3 -3
- package/src/components/agent/ShellStrip.tsx +43 -36
- package/src/components/agent/ShellTerminal.tsx +1 -1
- package/src/components/agent/SubagentStrip.tsx +13 -30
- package/src/components/agent/ToolCallCard.tsx +18 -41
- package/src/components/agent/Transcript.tsx +111 -93
- package/src/components/agent/TranscriptItemView.tsx +80 -67
- package/src/components/agent/TranscriptRows.tsx +4 -2
- package/src/components/agent/composer-commands.ts +80 -1
- package/src/components/agent/image-viewer.tsx +326 -0
- package/src/components/agent/markdown-image.tsx +139 -0
- package/src/components/agent/scrubber-marks.ts +33 -11
- package/src/components/agent/scrubber-peek.tsx +122 -0
- package/src/components/agent/session-panel-providers.tsx +41 -0
- package/src/components/agent/shell-actions.tsx +38 -1
- package/src/components/agent/tool-result-image.tsx +40 -0
- package/src/components/agent/transcript-variant.tsx +3 -21
- package/src/components/agent/use-catch-up.ts +20 -0
- package/src/components/agent/use-frame.ts +88 -0
- package/src/components/agent/use-host-image.ts +81 -0
- package/src/components/agent/use-shell-frame.ts +12 -70
- package/src/components/agent/use-subagent-frame.ts +19 -75
- package/src/components/prompt-area/prompt-area-engine.ts +21 -20
- package/src/components/prompt-area/prompt-area-list-ops.ts +12 -15
- package/src/components/prompt-area/use-prompt-area-keydown.ts +4 -2
- package/src/components/terminal/PermissionPrompt.tsx +31 -28
- package/src/components/terminal/QuestionPrompt.tsx +10 -46
- package/src/components/terminal/TerminalTranscript.tsx +23 -89
- package/src/components/terminal/affordances.tsx +100 -74
- package/src/components/terminal/height.ts +48 -32
- package/src/components/terminal/image-box.ts +31 -0
- package/src/components/terminal/items.tsx +70 -30
- package/src/components/terminal/markdown.tsx +4 -1
- package/src/components/terminal/scrubber.tsx +25 -272
- package/src/components/terminal/shell-row.ts +17 -3
- package/src/components/ui/Popover.tsx +29 -0
- package/src/components/ui/Select.tsx +25 -0
- package/src/index.ts +65 -12
- package/src/lib/file-link.ts +3 -16
- package/src/lib/permission-prompt.ts +57 -0
- package/src/lib/question-answers.ts +65 -0
- package/src/lib/shell-request.ts +115 -0
- package/src/lib/tool-icon.ts +13 -1
- package/src/styles/terminal.css +129 -22
- package/src/styles/theme.css +4 -0
- package/build/use-path-links-BgWXmfAS.mjs.map +0 -1
- package/src/components/prompt-area/use-markdown-mode.ts +0 -103
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { PermissionRequest } from '@workerdeck/protocol'
|
|
2
|
+
import { planFromRequest } from './plan-request.ts'
|
|
3
|
+
import { shellRequestPayload, shellRequestTitle, type ShellRequestPayload } from './shell-request.ts'
|
|
4
|
+
|
|
5
|
+
export type PermissionChoiceKey = 'allow' | 'deny' | 'stop'
|
|
6
|
+
|
|
7
|
+
export type PermissionChoice = {
|
|
8
|
+
key: PermissionChoiceKey
|
|
9
|
+
button: string
|
|
10
|
+
option: string
|
|
11
|
+
danger?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type PermissionPromptModel = {
|
|
15
|
+
plan?: string
|
|
16
|
+
shell?: ShellRequestPayload
|
|
17
|
+
heading: string
|
|
18
|
+
question: string
|
|
19
|
+
description?: string
|
|
20
|
+
decisionReason?: string
|
|
21
|
+
choices: [PermissionChoice, PermissionChoice, PermissionChoice]
|
|
22
|
+
denyPlaceholder: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type PermissionRequestLike = Pick<PermissionRequest, 'toolName' | 'input' | 'title' | 'displayName' | 'description' | 'decisionReason'>
|
|
26
|
+
|
|
27
|
+
export function permissionPromptModel(request: PermissionRequestLike): PermissionPromptModel {
|
|
28
|
+
const plan = planFromRequest(request)
|
|
29
|
+
const shell = plan ? undefined : shellRequestPayload(request)
|
|
30
|
+
const context = { description: request.description, decisionReason: request.decisionReason }
|
|
31
|
+
if (plan) {
|
|
32
|
+
return {
|
|
33
|
+
plan,
|
|
34
|
+
heading: 'Plan ready for review',
|
|
35
|
+
question: 'Ready to implement this plan?',
|
|
36
|
+
...context,
|
|
37
|
+
choices: [
|
|
38
|
+
{ key: 'allow', button: 'Approve plan', option: 'Approve plan' },
|
|
39
|
+
{ key: 'deny', button: 'Keep planning', option: 'Keep planning - tell it what to change' },
|
|
40
|
+
{ key: 'stop', button: 'Stop the turn', option: 'No, and stop the turn', danger: true },
|
|
41
|
+
],
|
|
42
|
+
denyPlaceholder: 'What should change? (optional) - the agent keeps planning and reads this',
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
shell,
|
|
47
|
+
heading: shell ? shellRequestTitle(shell) : (request.title ?? request.displayName ?? 'Permission needed'),
|
|
48
|
+
question: shell ? 'Do you want to let it?' : 'Do you want to proceed?',
|
|
49
|
+
...context,
|
|
50
|
+
choices: [
|
|
51
|
+
{ key: 'allow', button: 'Allow', option: 'Yes' },
|
|
52
|
+
{ key: 'deny', button: 'Deny', option: 'No, and tell the agent what to do differently' },
|
|
53
|
+
{ key: 'stop', button: 'Deny & stop', option: 'No, and stop the turn', danger: true },
|
|
54
|
+
],
|
|
55
|
+
denyPlaceholder: 'Reason (optional) - the agent reads this and can try something else',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import type { UserQuestion } from '@workerdeck/protocol'
|
|
3
|
+
|
|
4
|
+
export type Selection = { labels: string[]; other: string; otherActive: boolean }
|
|
5
|
+
|
|
6
|
+
export const EMPTY_SELECTION: Selection = { labels: [], other: '', otherActive: false }
|
|
7
|
+
|
|
8
|
+
export function answerFor(selection: Selection): string {
|
|
9
|
+
const parts = [...selection.labels]
|
|
10
|
+
if (selection.otherActive && selection.other.trim()) {
|
|
11
|
+
parts.push(selection.other.trim())
|
|
12
|
+
}
|
|
13
|
+
return parts.join(', ')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toggleLabel(selection: Selection, label: string, multiSelect: boolean): Selection {
|
|
17
|
+
if (!multiSelect) {
|
|
18
|
+
return { ...selection, labels: [label], otherActive: false }
|
|
19
|
+
}
|
|
20
|
+
const labels = selection.labels.includes(label) ? selection.labels.filter((l) => l !== label) : [...selection.labels, label]
|
|
21
|
+
return { ...selection, labels }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function toggleOther(selection: Selection, multiSelect: boolean): Selection {
|
|
25
|
+
const otherActive = !selection.otherActive
|
|
26
|
+
return { ...selection, otherActive, labels: otherActive && !multiSelect ? [] : selection.labels }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function answersFor(questions: readonly UserQuestion[], selections: readonly Selection[]): Record<string, string> {
|
|
30
|
+
const answers: Record<string, string> = {}
|
|
31
|
+
questions.forEach((question, index) => {
|
|
32
|
+
answers[question.question] = answerFor(selections[index] ?? EMPTY_SELECTION)
|
|
33
|
+
})
|
|
34
|
+
return answers
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface QuestionAnswers {
|
|
38
|
+
selections: Selection[]
|
|
39
|
+
selectionFor: (index: number) => Selection
|
|
40
|
+
answered: (index: number) => boolean
|
|
41
|
+
complete: boolean
|
|
42
|
+
update: (index: number, patch: Partial<Selection>) => void
|
|
43
|
+
toggle: (index: number, label: string) => void
|
|
44
|
+
toggleOther: (index: number) => void
|
|
45
|
+
answers: () => Record<string, string>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useQuestionAnswers(questions: readonly UserQuestion[]): QuestionAnswers {
|
|
49
|
+
const [selections, setSelections] = useState<Selection[]>(() => questions.map(() => EMPTY_SELECTION))
|
|
50
|
+
const selectionFor = (index: number) => selections[index] ?? EMPTY_SELECTION
|
|
51
|
+
const answered = (index: number) => answerFor(selectionFor(index)) !== ''
|
|
52
|
+
const edit = (index: number, change: (selection: Selection) => Selection) =>
|
|
53
|
+
setSelections((prev) => questions.map((_, i) => (i === index ? change(prev[i] ?? EMPTY_SELECTION) : (prev[i] ?? EMPTY_SELECTION))))
|
|
54
|
+
const multiSelect = (index: number) => questions[index]?.multiSelect === true
|
|
55
|
+
return {
|
|
56
|
+
selections,
|
|
57
|
+
selectionFor,
|
|
58
|
+
answered,
|
|
59
|
+
complete: questions.every((_, index) => answered(index)),
|
|
60
|
+
update: (index, patch) => edit(index, (selection) => ({ ...selection, ...patch })),
|
|
61
|
+
toggle: (index, label) => edit(index, (selection) => toggleLabel(selection, label, multiSelect(index))),
|
|
62
|
+
toggleOther: (index) => edit(index, (selection) => toggleOther(selection, multiSelect(index))),
|
|
63
|
+
answers: () => answersFor(questions, selections),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// The agent's shell write tools on a permission card. Allow-lists key on the tool name, and the danger is in the
|
|
2
|
+
// payload, so the card shows the command, the typed text and the keys verbatim rather than a JSON summary.
|
|
3
|
+
export type ShellRequestPayload =
|
|
4
|
+
| { kind: 'run'; command: string }
|
|
5
|
+
| { kind: 'write'; shellId: string; data?: string; keys?: string[] }
|
|
6
|
+
| { kind: 'kill'; shellId: string }
|
|
7
|
+
| { kind: 'grant'; shellId: string; reason: string }
|
|
8
|
+
|
|
9
|
+
type RequestLike = { toolName?: unknown; input?: unknown }
|
|
10
|
+
|
|
11
|
+
const CONTROL_NAMES: Record<string, string> = {
|
|
12
|
+
'\r': 'enter',
|
|
13
|
+
'\n': 'newline',
|
|
14
|
+
'\t': 'tab',
|
|
15
|
+
'\x1b': 'escape',
|
|
16
|
+
'\x7f': 'backspace',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ShellWriteTool = 'shell_run' | 'shell_write' | 'shell_kill' | 'shell_request_write'
|
|
20
|
+
|
|
21
|
+
const SHELL_WRITE_TOOLS: readonly string[] = ['shell_run', 'shell_write', 'shell_kill', 'shell_request_write']
|
|
22
|
+
|
|
23
|
+
export function shellToolOf(toolName: unknown): ShellWriteTool | undefined {
|
|
24
|
+
if (typeof toolName !== 'string') {
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
const bare = toolName.startsWith('mcp__') ? toolName.slice(toolName.indexOf('__', 5) + 2) : toolName
|
|
28
|
+
return SHELL_WRITE_TOOLS.includes(bare) ? (bare as ShellWriteTool) : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function shellRequestPayload(request: RequestLike | null | undefined): ShellRequestPayload | undefined {
|
|
32
|
+
const tool = shellToolOf(request?.toolName)
|
|
33
|
+
if (!tool) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
const input = request?.input !== null && typeof request?.input === 'object' ? (request.input as Record<string, unknown>) : {}
|
|
37
|
+
const shellId = typeof input.shellId === 'string' ? input.shellId : ''
|
|
38
|
+
switch (tool) {
|
|
39
|
+
case 'shell_run': {
|
|
40
|
+
return { kind: 'run', command: typeof input.command === 'string' ? input.command : '' }
|
|
41
|
+
}
|
|
42
|
+
case 'shell_write': {
|
|
43
|
+
const keys = Array.isArray(input.keys) ? input.keys.filter((key): key is string => typeof key === 'string') : undefined
|
|
44
|
+
return {
|
|
45
|
+
kind: 'write',
|
|
46
|
+
shellId,
|
|
47
|
+
...(typeof input.data === 'string' ? { data: input.data } : {}),
|
|
48
|
+
...(keys && keys.length > 0 ? { keys } : {}),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
case 'shell_kill': {
|
|
52
|
+
return { kind: 'kill', shellId }
|
|
53
|
+
}
|
|
54
|
+
case 'shell_request_write': {
|
|
55
|
+
return { kind: 'grant', shellId, reason: typeof input.reason === 'string' ? input.reason : '' }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function shellRequestTitle(payload: ShellRequestPayload): string {
|
|
61
|
+
switch (payload.kind) {
|
|
62
|
+
case 'run': {
|
|
63
|
+
return 'Agent wants to start a shell'
|
|
64
|
+
}
|
|
65
|
+
case 'write': {
|
|
66
|
+
return `Agent wants to type into shell ${payload.shellId}`
|
|
67
|
+
}
|
|
68
|
+
case 'kill': {
|
|
69
|
+
return `Agent wants to kill shell ${payload.shellId}`
|
|
70
|
+
}
|
|
71
|
+
case 'grant': {
|
|
72
|
+
return `Agent asks to type into your shell ${payload.shellId}`
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The lines drawn verbatim under the title. Typed text keeps its shape; a control character is named in brackets
|
|
78
|
+
// so a hidden Enter or Escape is visible on the card.
|
|
79
|
+
export function shellRequestLines(payload: ShellRequestPayload): string[] {
|
|
80
|
+
switch (payload.kind) {
|
|
81
|
+
case 'run': {
|
|
82
|
+
return payload.command.split('\n').map((line, index) => (index === 0 ? `$ ${line}` : ` ${line}`))
|
|
83
|
+
}
|
|
84
|
+
case 'write': {
|
|
85
|
+
const lines: string[] = []
|
|
86
|
+
if (payload.data !== undefined) {
|
|
87
|
+
lines.push(`types: ${visibleControls(payload.data)}`)
|
|
88
|
+
}
|
|
89
|
+
if (payload.keys) {
|
|
90
|
+
lines.push(`presses: ${payload.keys.map((key) => `[${key}]`).join(' ')}`)
|
|
91
|
+
}
|
|
92
|
+
return lines
|
|
93
|
+
}
|
|
94
|
+
case 'kill': {
|
|
95
|
+
return [`kill ${payload.shellId}`]
|
|
96
|
+
}
|
|
97
|
+
case 'grant': {
|
|
98
|
+
return [`why: ${visibleControls(payload.reason)}`, 'until the shell ends or you revoke it']
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function visibleControls(text: string): string {
|
|
104
|
+
let out = ''
|
|
105
|
+
for (const char of text) {
|
|
106
|
+
const code = char.codePointAt(0)!
|
|
107
|
+
if (code >= 0x20 && code !== 0x7f) {
|
|
108
|
+
out += char
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
const named = CONTROL_NAMES[char]
|
|
112
|
+
out += named ? `[${named}]` : `[ctrl-${String.fromCharCode(code + 64).toLowerCase()}]`
|
|
113
|
+
}
|
|
114
|
+
return out
|
|
115
|
+
}
|
package/src/lib/tool-icon.ts
CHANGED
|
@@ -22,7 +22,19 @@ export function toolIcon(toolName: string): LucideIcon {
|
|
|
22
22
|
switch (toolName) {
|
|
23
23
|
case 'Bash':
|
|
24
24
|
case 'BashOutput':
|
|
25
|
-
case 'KillShell':
|
|
25
|
+
case 'KillShell':
|
|
26
|
+
case 'shell_list':
|
|
27
|
+
case 'shell_read':
|
|
28
|
+
case 'shell_run':
|
|
29
|
+
case 'shell_write':
|
|
30
|
+
case 'shell_kill':
|
|
31
|
+
case 'shell_request_write':
|
|
32
|
+
case 'mcp__workerdeck__shell_list':
|
|
33
|
+
case 'mcp__workerdeck__shell_read':
|
|
34
|
+
case 'mcp__workerdeck__shell_run':
|
|
35
|
+
case 'mcp__workerdeck__shell_write':
|
|
36
|
+
case 'mcp__workerdeck__shell_kill':
|
|
37
|
+
case 'mcp__workerdeck__shell_request_write': {
|
|
26
38
|
return Terminal
|
|
27
39
|
}
|
|
28
40
|
case 'Read': {
|
package/src/styles/terminal.css
CHANGED
|
@@ -436,46 +436,144 @@
|
|
|
436
436
|
.term-hoverable {
|
|
437
437
|
position: relative;
|
|
438
438
|
}
|
|
439
|
-
/*
|
|
440
|
-
|
|
441
|
-
|
|
439
|
+
/* `below` sits in the blank line (cards: the row gap) under the block; `inline`
|
|
440
|
+
pins to the block's first line at its right edge, for blocks with no blank
|
|
441
|
+
line beneath them. Absolute either way, so no row moves when they appear. */
|
|
442
|
+
:is([data-terminal], [data-cards]) .term-actions {
|
|
442
443
|
position: absolute;
|
|
443
|
-
|
|
444
|
-
right: 0;
|
|
445
|
-
height: var(--term-line);
|
|
444
|
+
z-index: 1;
|
|
446
445
|
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
opacity: 0;
|
|
448
|
+
transition: opacity var(--motion-fast, 120ms);
|
|
449
|
+
}
|
|
450
|
+
[data-terminal] .term-actions {
|
|
451
|
+
height: var(--term-line);
|
|
447
452
|
gap: 1ch;
|
|
453
|
+
}
|
|
454
|
+
[data-cards] .term-actions {
|
|
455
|
+
height: 20px;
|
|
456
|
+
gap: 6px;
|
|
457
|
+
}
|
|
458
|
+
:is([data-terminal], [data-cards]) [data-placement='below'] > .term-actions {
|
|
459
|
+
top: 100%;
|
|
460
|
+
}
|
|
461
|
+
[data-terminal] [data-placement='below'] > .term-actions {
|
|
462
|
+
left: var(--term-cell);
|
|
463
|
+
}
|
|
464
|
+
[data-cards] [data-placement='below'] > .term-actions {
|
|
465
|
+
left: 0;
|
|
466
|
+
}
|
|
467
|
+
:is([data-terminal], [data-cards]) [data-placement='inline'] > .term-actions {
|
|
468
|
+
top: 0;
|
|
469
|
+
right: 0;
|
|
448
470
|
padding-inline: 1ch;
|
|
449
471
|
background: var(--bg, transparent);
|
|
450
|
-
opacity: 0;
|
|
451
|
-
transition: opacity var(--motion-fast, 120ms);
|
|
452
472
|
}
|
|
453
|
-
|
|
454
|
-
|
|
473
|
+
/* Only the innermost `below` block under the pointer shows its bar, so an
|
|
474
|
+
expanded chain does not light up its own and its parent's at once. */
|
|
475
|
+
:is([data-terminal], [data-cards])
|
|
476
|
+
[data-placement='below']:is(:hover, :focus-within):not(:has([data-placement='below']:is(:hover, :focus-within)))
|
|
477
|
+
> .term-actions,
|
|
478
|
+
:is([data-terminal], [data-cards]) [data-placement='inline']:is(:hover, :focus-within) > .term-actions {
|
|
455
479
|
opacity: 1;
|
|
456
480
|
}
|
|
481
|
+
/* A virtualized row paints under the next one, whose top padding is the gap
|
|
482
|
+
the bar sits in; lift the hovered row so the bar stays clickable. */
|
|
483
|
+
:is([data-terminal], [data-cards]) [data-slot='transcript-rows'] [data-index]:not(.absolute) {
|
|
484
|
+
position: relative;
|
|
485
|
+
}
|
|
486
|
+
:is([data-terminal], [data-cards]) [data-slot='transcript-rows'] [data-index]:is(:hover, :focus-within) {
|
|
487
|
+
z-index: 1;
|
|
488
|
+
}
|
|
457
489
|
@media (hover: none) {
|
|
458
|
-
|
|
459
|
-
all, and a phone has the room. */
|
|
460
|
-
[data-terminal] .term-actions {
|
|
490
|
+
:is([data-terminal], [data-cards]) .term-actions {
|
|
461
491
|
opacity: 1;
|
|
462
492
|
}
|
|
463
493
|
}
|
|
494
|
+
[data-cards] .term-action {
|
|
495
|
+
font: inherit;
|
|
496
|
+
font-size: 12px;
|
|
497
|
+
font-weight: 500;
|
|
498
|
+
line-height: 1;
|
|
499
|
+
height: 18px;
|
|
500
|
+
min-width: 22px;
|
|
501
|
+
display: inline-flex;
|
|
502
|
+
align-items: center;
|
|
503
|
+
justify-content: center;
|
|
504
|
+
gap: 4px;
|
|
505
|
+
padding-inline: 5px;
|
|
506
|
+
color: var(--fg-3);
|
|
507
|
+
background: var(--surface);
|
|
508
|
+
border: 1px solid var(--border);
|
|
509
|
+
border-radius: 4px;
|
|
510
|
+
white-space: nowrap;
|
|
511
|
+
cursor: pointer;
|
|
512
|
+
}
|
|
513
|
+
[data-cards] .term-action-icon {
|
|
514
|
+
width: 14px;
|
|
515
|
+
height: 14px;
|
|
516
|
+
stroke-width: 2.25;
|
|
517
|
+
flex: none;
|
|
518
|
+
}
|
|
519
|
+
[data-cards] .term-action-word {
|
|
520
|
+
padding-right: 2px;
|
|
521
|
+
}
|
|
522
|
+
[data-cards] .term-action:hover,
|
|
523
|
+
[data-cards] .term-action:focus-visible {
|
|
524
|
+
color: var(--fg-1);
|
|
525
|
+
background: var(--row-hover);
|
|
526
|
+
}
|
|
527
|
+
[data-cards] .term-action[data-tone='magenta'] {
|
|
528
|
+
color: var(--wd-shell-accent);
|
|
529
|
+
}
|
|
530
|
+
[data-cards] .term-action[data-tone='yellow'] {
|
|
531
|
+
color: var(--warning);
|
|
532
|
+
}
|
|
533
|
+
[data-cards] .term-action[data-tone='danger']:hover {
|
|
534
|
+
color: var(--danger);
|
|
535
|
+
}
|
|
464
536
|
[data-terminal] .term-action {
|
|
465
537
|
font: inherit;
|
|
466
|
-
|
|
538
|
+
font-weight: 500;
|
|
539
|
+
line-height: 1;
|
|
540
|
+
height: calc(var(--term-line) - 2px);
|
|
541
|
+
min-width: 3ch;
|
|
542
|
+
display: inline-flex;
|
|
543
|
+
align-items: center;
|
|
544
|
+
justify-content: center;
|
|
545
|
+
gap: 0.6ch;
|
|
546
|
+
padding-inline: 0.6ch;
|
|
467
547
|
color: var(--term-dim);
|
|
468
|
-
background:
|
|
469
|
-
border:
|
|
470
|
-
|
|
548
|
+
background: var(--term-band-bg);
|
|
549
|
+
border: 1px solid color-mix(in srgb, var(--term-faint) 55%, transparent);
|
|
550
|
+
border-radius: 3px;
|
|
551
|
+
white-space: pre;
|
|
471
552
|
cursor: pointer;
|
|
472
553
|
}
|
|
473
|
-
|
|
554
|
+
[data-terminal] .term-action-icon {
|
|
555
|
+
width: calc(var(--term-font-size) + 1px);
|
|
556
|
+
height: calc(var(--term-font-size) + 1px);
|
|
557
|
+
stroke-width: 2.25;
|
|
558
|
+
flex: none;
|
|
559
|
+
}
|
|
560
|
+
[data-terminal] .term-action-word {
|
|
561
|
+
padding-right: 0.2ch;
|
|
562
|
+
}
|
|
563
|
+
[data-terminal] .term-action:hover,
|
|
564
|
+
[data-terminal] .term-action:focus-visible {
|
|
565
|
+
color: var(--term-bright);
|
|
566
|
+
border-color: var(--term-dim);
|
|
567
|
+
background: var(--term-row-hover);
|
|
568
|
+
}
|
|
474
569
|
[data-terminal] .term-action[data-tone='magenta'] {
|
|
475
570
|
color: var(--term-magenta);
|
|
476
571
|
}
|
|
477
|
-
[data-terminal] .term-action
|
|
478
|
-
color: var(--term-
|
|
572
|
+
[data-terminal] .term-action[data-tone='yellow'] {
|
|
573
|
+
color: var(--term-yellow);
|
|
574
|
+
}
|
|
575
|
+
[data-terminal] .term-action[data-tone='danger']:hover {
|
|
576
|
+
color: var(--term-red);
|
|
479
577
|
}
|
|
480
578
|
|
|
481
579
|
/* What you typed, on a band of its own - the CLI's own treatment. */
|
|
@@ -714,7 +812,7 @@ button.term-link {
|
|
|
714
812
|
[data-terminal] [data-sticky-lane] > [data-sticky-headlane] {
|
|
715
813
|
position: absolute;
|
|
716
814
|
inset: 0;
|
|
717
|
-
z-index:
|
|
815
|
+
z-index: 2;
|
|
718
816
|
pointer-events: none;
|
|
719
817
|
}
|
|
720
818
|
|
|
@@ -1276,7 +1374,16 @@ button.term-link {
|
|
|
1276
1374
|
display: block;
|
|
1277
1375
|
overflow: hidden;
|
|
1278
1376
|
}
|
|
1279
|
-
.term-image >
|
|
1377
|
+
.term-image > [data-slot='viewable-image'] {
|
|
1378
|
+
display: block;
|
|
1379
|
+
height: 100%;
|
|
1380
|
+
max-width: 100%;
|
|
1381
|
+
padding: 0;
|
|
1382
|
+
border: 0;
|
|
1383
|
+
background: none;
|
|
1384
|
+
}
|
|
1385
|
+
.term-image img {
|
|
1386
|
+
display: block;
|
|
1280
1387
|
max-width: 100%;
|
|
1281
1388
|
height: 100%;
|
|
1282
1389
|
object-fit: contain;
|
package/src/styles/theme.css
CHANGED
|
@@ -821,6 +821,10 @@
|
|
|
821
821
|
left: 0;
|
|
822
822
|
width: 12px;
|
|
823
823
|
}
|
|
824
|
+
/* xterm.css paints its viewport #000 whatever the theme says; the drill-in shows the panel through. */
|
|
825
|
+
.wd-shell-term .xterm .xterm-viewport {
|
|
826
|
+
background-color: transparent;
|
|
827
|
+
}
|
|
824
828
|
.wd-scrubber .wd-scrub-mark[data-kind='user'] {
|
|
825
829
|
background: var(--accent);
|
|
826
830
|
}
|