@workerdeck/ui 0.21.0 → 0.22.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 +3 -2
- package/build/{SessionPanel-BjNAnWMF.d.mts → SessionPanel-13l25ubU.d.mts} +15 -5
- package/build/{SessionPanel-DNuPKg5c.mjs → SessionPanel-DgwjH4Ve.mjs} +624 -221
- package/build/SessionPanel-DgwjH4Ve.mjs.map +1 -0
- package/build/index.d.mts +54 -21
- package/build/index.mjs +7 -6
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +3547 -0
- package/build/workspace.d.mts +1 -1
- package/build/workspace.mjs +1 -1
- package/package.json +10 -6
- package/src/components/agent/Composer.tsx +10 -10
- package/src/components/agent/ContextDialog.tsx +3 -2
- package/src/components/agent/Conversation.tsx +1 -1
- package/src/components/agent/McpDialog.tsx +3 -1
- package/src/components/agent/Scrubber.tsx +248 -0
- package/src/components/agent/SessionList.tsx +3 -1
- package/src/components/agent/SessionPanel.tsx +20 -7
- package/src/components/agent/SessionSteps.tsx +10 -4
- package/src/components/agent/SkillsDialog.tsx +3 -2
- package/src/components/agent/StatusBar.tsx +1 -1
- package/src/components/agent/Transcript.tsx +147 -73
- package/src/components/agent/UsageDialog.tsx +3 -1
- package/src/components/agent/scrubber-marks.ts +277 -0
- package/src/components/terminal/PermissionPrompt.tsx +3 -0
- package/src/components/terminal/QuestionPrompt.tsx +3 -0
- package/src/components/terminal/StatusLine.tsx +3 -1
- package/src/components/ui/AlertDialog.tsx +23 -20
- package/src/components/ui/Dialog.tsx +26 -23
- package/src/components/ui/Menu.tsx +20 -17
- package/src/components/ui/PortalScope.tsx +27 -0
- package/src/components/ui/Select.tsx +19 -16
- package/src/components/ui/Tooltip.tsx +13 -10
- package/src/styles/scoped.entry.css +16 -0
- package/src/styles/terminal.css +1 -1
- package/src/styles/theme.css +160 -0
- package/build/SessionPanel-DNuPKg5c.mjs.map +0 -1
|
@@ -27,12 +27,14 @@ export interface TerminalPermissionPromptProps {
|
|
|
27
27
|
/** `message` is fed back to the agent, which can then try something else;
|
|
28
28
|
* `interrupt` also stops the turn. */
|
|
29
29
|
onDeny: (requestId: string, message?: string, interrupt?: boolean) => void
|
|
30
|
+
className?: string
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export function TerminalPermissionPrompt({
|
|
33
34
|
request,
|
|
34
35
|
onApprove,
|
|
35
36
|
onDeny,
|
|
37
|
+
className,
|
|
36
38
|
}: TerminalPermissionPromptProps) {
|
|
37
39
|
const [focused, setFocused] = useState(0)
|
|
38
40
|
const [denying, setDenying] = useState(false)
|
|
@@ -75,6 +77,7 @@ export function TerminalPermissionPrompt({
|
|
|
75
77
|
return (
|
|
76
78
|
<div
|
|
77
79
|
data-slot='permission-prompt'
|
|
80
|
+
className={className}
|
|
78
81
|
onKeyDown={(event) => {
|
|
79
82
|
if (event.key === 'Escape') {
|
|
80
83
|
event.preventDefault()
|
|
@@ -40,12 +40,14 @@ export interface TerminalQuestionPromptProps {
|
|
|
40
40
|
onAnswer: (requestId: string, updatedInput: Record<string, unknown>) => void
|
|
41
41
|
/** Deny the tool — the model proceeds without an answer. */
|
|
42
42
|
onDismiss: (requestId: string, message?: string) => void
|
|
43
|
+
className?: string
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export function TerminalQuestionPrompt({
|
|
46
47
|
request,
|
|
47
48
|
onAnswer,
|
|
48
49
|
onDismiss,
|
|
50
|
+
className,
|
|
49
51
|
}: TerminalQuestionPromptProps) {
|
|
50
52
|
const questions = parseUserQuestions(request.input)
|
|
51
53
|
const [selections, setSelections] = useState<Selection[]>(() => questions.map(() => EMPTY))
|
|
@@ -97,6 +99,7 @@ export function TerminalQuestionPrompt({
|
|
|
97
99
|
return (
|
|
98
100
|
<div
|
|
99
101
|
data-slot='question-prompt'
|
|
102
|
+
className={className}
|
|
100
103
|
onKeyDown={(event) => {
|
|
101
104
|
if (event.key === 'Escape') {
|
|
102
105
|
event.preventDefault()
|
|
@@ -38,6 +38,7 @@ const SEVERITY_TONE: Record<StatusSeverity, Tone> = {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface TerminalStatusLineProps {
|
|
41
|
+
className?: string
|
|
41
42
|
state: TranscriptState
|
|
42
43
|
/** Plan windows to read from, when they should not be the session's own — the
|
|
43
44
|
* gateway's per-profile state merged over this transcript's. See
|
|
@@ -75,6 +76,7 @@ function Reading({
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export function TerminalStatusLine({
|
|
79
|
+
className,
|
|
78
80
|
state,
|
|
79
81
|
rateLimits,
|
|
80
82
|
connection,
|
|
@@ -147,7 +149,7 @@ export function TerminalStatusLine({
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
return (
|
|
150
|
-
<Row data-slot='status-line' tone='dim'>
|
|
152
|
+
<Row data-slot='status-line' tone='dim' className={className}>
|
|
151
153
|
{parts.map((part, index) => (
|
|
152
154
|
<span key={index}>
|
|
153
155
|
{index > 0 ? <Ink tone='faint'> · </Ink> : null}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'react'
|
|
2
2
|
import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'
|
|
3
3
|
import { cn } from '../../lib/utils.ts'
|
|
4
|
+
import { PortalScope } from './PortalScope.tsx'
|
|
4
5
|
|
|
5
6
|
export const AlertDialog = AlertDialogPrimitive.Root
|
|
6
7
|
export const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
|
@@ -12,26 +13,28 @@ export const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Pr
|
|
|
12
13
|
...props
|
|
13
14
|
}) => (
|
|
14
15
|
<AlertDialogPrimitive.Portal>
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
<PortalScope>
|
|
17
|
+
<AlertDialogPrimitive.Backdrop
|
|
18
|
+
className={cn(
|
|
19
|
+
'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',
|
|
20
|
+
'transition-opacity duration-(--motion-base)',
|
|
21
|
+
'data-starting-style:opacity-0 data-ending-style:opacity-0',
|
|
22
|
+
)}
|
|
23
|
+
/>
|
|
24
|
+
<AlertDialogPrimitive.Popup
|
|
25
|
+
data-slot='alert-dialog-content'
|
|
26
|
+
className={cn(
|
|
27
|
+
'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',
|
|
28
|
+
'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',
|
|
29
|
+
'transition-[opacity,transform] duration-(--motion-base)',
|
|
30
|
+
'data-starting-style:scale-95 data-starting-style:opacity-0',
|
|
31
|
+
'data-ending-style:scale-95 data-ending-style:opacity-0',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}>
|
|
35
|
+
{children}
|
|
36
|
+
</AlertDialogPrimitive.Popup>
|
|
37
|
+
</PortalScope>
|
|
35
38
|
</AlertDialogPrimitive.Portal>
|
|
36
39
|
)
|
|
37
40
|
|
|
@@ -2,6 +2,7 @@ import { type FunctionComponent } from 'react'
|
|
|
2
2
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
3
3
|
import { X } from 'lucide-react'
|
|
4
4
|
import { cn } from '../../lib/utils.ts'
|
|
5
|
+
import { PortalScope } from './PortalScope.tsx'
|
|
5
6
|
|
|
6
7
|
export const Dialog = DialogPrimitive.Root
|
|
7
8
|
export const DialogTrigger = DialogPrimitive.Trigger
|
|
@@ -18,29 +19,31 @@ export const DialogContent: FunctionComponent<
|
|
|
18
19
|
DialogPrimitive.Popup.Props & { size?: 'sm' | 'md' | 'lg' }
|
|
19
20
|
> = ({ className, children, size = 'md', ...props }) => (
|
|
20
21
|
<DialogPrimitive.Portal>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
<PortalScope>
|
|
23
|
+
<DialogPrimitive.Backdrop
|
|
24
|
+
className={cn(
|
|
25
|
+
'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',
|
|
26
|
+
'transition-opacity duration-(--motion-base)',
|
|
27
|
+
'data-starting-style:opacity-0 data-ending-style:opacity-0',
|
|
28
|
+
)}
|
|
29
|
+
/>
|
|
30
|
+
<DialogPrimitive.Popup
|
|
31
|
+
data-slot='dialog-content'
|
|
32
|
+
className={cn(
|
|
33
|
+
'fixed top-1/2 left-1/2 z-70 flex max-h-[min(42rem,calc(100dvh-3rem))] -translate-x-1/2 -translate-y-1/2 flex-col',
|
|
34
|
+
size === 'sm' && 'w-[min(24rem,calc(100vw-2rem))]',
|
|
35
|
+
size === 'md' && 'w-[min(32rem,calc(100vw-2rem))]',
|
|
36
|
+
size === 'lg' && 'w-[min(46rem,calc(100vw-2rem))]',
|
|
37
|
+
'rounded-lg border border-border bg-surface shadow-(--shadow-lg) outline-none',
|
|
38
|
+
'transition-[opacity,transform] duration-(--motion-base)',
|
|
39
|
+
'data-starting-style:scale-95 data-starting-style:opacity-0',
|
|
40
|
+
'data-ending-style:scale-95 data-ending-style:opacity-0',
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
{...props}>
|
|
44
|
+
{children}
|
|
45
|
+
</DialogPrimitive.Popup>
|
|
46
|
+
</PortalScope>
|
|
44
47
|
</DialogPrimitive.Portal>
|
|
45
48
|
)
|
|
46
49
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'react'
|
|
2
2
|
import { Menu as MenuPrimitive } from '@base-ui/react/menu'
|
|
3
3
|
import { cn } from '../../lib/utils.ts'
|
|
4
|
+
import { PortalScope } from './PortalScope.tsx'
|
|
4
5
|
|
|
5
6
|
export const Menu = MenuPrimitive.Root
|
|
6
7
|
export const MenuTrigger = MenuPrimitive.Trigger
|
|
@@ -10,23 +11,25 @@ export const MenuContent: FunctionComponent<
|
|
|
10
11
|
Pick<MenuPrimitive.Positioner.Props, 'align' | 'side' | 'sideOffset'>
|
|
11
12
|
> = ({ className, align = 'end', side = 'bottom', sideOffset = 6, ...props }) => (
|
|
12
13
|
<MenuPrimitive.Portal>
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
<PortalScope>
|
|
15
|
+
<MenuPrimitive.Positioner
|
|
16
|
+
align={align}
|
|
17
|
+
side={side}
|
|
18
|
+
sideOffset={sideOffset}
|
|
19
|
+
className='isolate z-80 outline-none'>
|
|
20
|
+
<MenuPrimitive.Popup
|
|
21
|
+
data-slot='menu-content'
|
|
22
|
+
className={cn(
|
|
23
|
+
'min-w-48 rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',
|
|
24
|
+
'transition-[opacity,transform] duration-(--motion-base)',
|
|
25
|
+
'data-starting-style:scale-95 data-starting-style:opacity-0',
|
|
26
|
+
'data-ending-style:scale-95 data-ending-style:opacity-0',
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
</MenuPrimitive.Positioner>
|
|
32
|
+
</PortalScope>
|
|
30
33
|
</MenuPrimitive.Portal>
|
|
31
34
|
)
|
|
32
35
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type FunctionComponent, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Re-establishes the `.wd-root` styling scope inside a portal.
|
|
5
|
+
*
|
|
6
|
+
* The scoped stylesheet (`@workerdeck/ui/scoped.css`) rewrites every rule to
|
|
7
|
+
* live under `.wd-root`, so an embedder's page keeps its own design system and
|
|
8
|
+
* the panel keeps this one. Base UI popups (Menu, Dialog, AlertDialog, Select,
|
|
9
|
+
* Tooltip) portal to `document.body` — OUTSIDE the embedder's wrapper — so
|
|
10
|
+
* without this they would render unstyled, or worse, styled by the host.
|
|
11
|
+
*
|
|
12
|
+
* `display: contents` (inline, so no stylesheet needs to load for it) makes the
|
|
13
|
+
* element generate no box: positioning, hit-testing and the popup's own layout
|
|
14
|
+
* are untouched. It still carries the class, which is all the scope needs —
|
|
15
|
+
* design tokens declared on `.wd-root` inherit through it, and every
|
|
16
|
+
* `.wd-root <x>` rule matches below it. Theme keeps working because the dark
|
|
17
|
+
* token block matches `[data-theme='dark']` on ANY ancestor, and `document.body`
|
|
18
|
+
* sits under the host's themed `<html>` just like the panel does.
|
|
19
|
+
*
|
|
20
|
+
* Under the classic `theme.css` integration the class matches no rule and the
|
|
21
|
+
* element is inert, so the wrapper is unconditional.
|
|
22
|
+
*/
|
|
23
|
+
export const PortalScope: FunctionComponent<{ children?: ReactNode }> = ({ children }) => (
|
|
24
|
+
<div className='wd-root' style={{ display: 'contents' }}>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
@@ -2,6 +2,7 @@ import { type FunctionComponent } from 'react'
|
|
|
2
2
|
import { Select as SelectPrimitive } from '@base-ui/react/select'
|
|
3
3
|
import { Check, ChevronsUpDown } from 'lucide-react'
|
|
4
4
|
import { cn } from '../../lib/utils.ts'
|
|
5
|
+
import { PortalScope } from './PortalScope.tsx'
|
|
5
6
|
|
|
6
7
|
export const Select = SelectPrimitive.Root
|
|
7
8
|
export const SelectValue = SelectPrimitive.Value
|
|
@@ -40,22 +41,24 @@ export const SelectContent: FunctionComponent<
|
|
|
40
41
|
...props
|
|
41
42
|
}) => (
|
|
42
43
|
<SelectPrimitive.Portal>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
<PortalScope>
|
|
45
|
+
<SelectPrimitive.Positioner
|
|
46
|
+
align={align}
|
|
47
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
48
|
+
side={side}
|
|
49
|
+
sideOffset={sideOffset}
|
|
50
|
+
className='isolate z-80 outline-none'>
|
|
51
|
+
<SelectPrimitive.Popup
|
|
52
|
+
data-slot='select-content'
|
|
53
|
+
className={cn(
|
|
54
|
+
'max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto',
|
|
55
|
+
'rounded-md border border-border bg-surface p-1 text-fg-1 shadow-(--shadow-lg) outline-none',
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
</SelectPrimitive.Positioner>
|
|
61
|
+
</PortalScope>
|
|
59
62
|
</SelectPrimitive.Portal>
|
|
60
63
|
)
|
|
61
64
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type FunctionComponent, type ReactElement, type ReactNode } from 'react'
|
|
2
2
|
import { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip'
|
|
3
3
|
import { cn } from '../../lib/utils.ts'
|
|
4
|
+
import { PortalScope } from './PortalScope.tsx'
|
|
4
5
|
|
|
5
6
|
export const TooltipProvider = TooltipPrimitive.Provider
|
|
6
7
|
|
|
@@ -8,16 +9,18 @@ export const TooltipContent: FunctionComponent<
|
|
|
8
9
|
TooltipPrimitive.Popup.Props & Pick<TooltipPrimitive.Positioner.Props, 'side' | 'sideOffset'>
|
|
9
10
|
> = ({ className, side = 'top', sideOffset = 6, ...props }) => (
|
|
10
11
|
<TooltipPrimitive.Portal>
|
|
11
|
-
<
|
|
12
|
-
<TooltipPrimitive.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
<PortalScope>
|
|
13
|
+
<TooltipPrimitive.Positioner side={side} sideOffset={sideOffset} className='isolate z-90'>
|
|
14
|
+
<TooltipPrimitive.Popup
|
|
15
|
+
data-slot='tooltip-content'
|
|
16
|
+
className={cn(
|
|
17
|
+
'rounded-md border border-border bg-surface px-2 py-1 text-label text-fg-2 shadow-(--shadow-md) outline-none',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
</TooltipPrimitive.Positioner>
|
|
23
|
+
</PortalScope>
|
|
21
24
|
</TooltipPrimitive.Portal>
|
|
22
25
|
)
|
|
23
26
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Build entry for the SCOPED stylesheet (`@workerdeck/ui/scoped.css`) — see
|
|
3
|
+
* `scripts/build-scoped-css.mjs`, which compiles this with the Tailwind CLI and
|
|
4
|
+
* then rewrites every selector under `.wd-root`. Not shipped as-is and not an
|
|
5
|
+
* export: embedders import the compiled `scoped.css` artifact.
|
|
6
|
+
*
|
|
7
|
+
* `source(none)` + explicit `@source`: the utilities compiled here are exactly
|
|
8
|
+
* the ones this package's own components (and streamdown, whose markdown
|
|
9
|
+
* renderer ships Tailwind-classed markup) use — hermetic, independent of any
|
|
10
|
+
* host app's Tailwind build.
|
|
11
|
+
*/
|
|
12
|
+
@import 'tailwindcss' source(none);
|
|
13
|
+
@source '../components';
|
|
14
|
+
@source '../lib';
|
|
15
|
+
@source '../../node_modules/streamdown/dist';
|
|
16
|
+
@import './theme.css';
|
package/src/styles/terminal.css
CHANGED
|
@@ -595,7 +595,7 @@
|
|
|
595
595
|
[data-terminal] .term-composer-body {
|
|
596
596
|
margin-inline: auto;
|
|
597
597
|
width: 100%;
|
|
598
|
-
max-width: var(--wd-
|
|
598
|
+
max-width: var(--wd-transcript-max-width);
|
|
599
599
|
padding-inline: var(--term-bleed, 0px);
|
|
600
600
|
/* Air on each side of the prompt, and a plain pixel value rather than a
|
|
601
601
|
fraction of `--term-line`: this is chrome between two rules, not a
|
package/src/styles/theme.css
CHANGED
|
@@ -151,6 +151,10 @@
|
|
|
151
151
|
--shine: #dcdcdc;
|
|
152
152
|
--frame-shadow: var(--shadow-xs);
|
|
153
153
|
|
|
154
|
+
/* The overview rail's bookmark magenta — the app palette has no magenta,
|
|
155
|
+
and every other colour on the rail is already spoken for. */
|
|
156
|
+
--wd-scrub-bookmark: #8b3a8b;
|
|
157
|
+
|
|
154
158
|
/* ---------- aliases (bridge for the @theme utilities) ---------- */
|
|
155
159
|
--surface: var(--bg-surface);
|
|
156
160
|
--sidebar: #f4f4f4;
|
|
@@ -234,6 +238,9 @@
|
|
|
234
238
|
0 4px 24px rgba(0, 0, 0, 0.55),
|
|
235
239
|
0 2px 6px rgba(0, 0, 0, 0.45);
|
|
236
240
|
|
|
241
|
+
/* See the light block — lifted for the dark ground. */
|
|
242
|
+
--wd-scrub-bookmark: #c586c0;
|
|
243
|
+
|
|
237
244
|
/* ---------- aliases ---------- */
|
|
238
245
|
--surface: var(--bg-surface);
|
|
239
246
|
--sidebar: #131313;
|
|
@@ -267,6 +274,12 @@
|
|
|
267
274
|
--motion-base: 150ms;
|
|
268
275
|
--motion-slow: 220ms;
|
|
269
276
|
|
|
277
|
+
/* ---------- Geometry (embedder-overridable layout knobs) ---------- */
|
|
278
|
+
--wd-status-bar-height: 38px;
|
|
279
|
+
--wd-composer-padding: 12px;
|
|
280
|
+
--wd-transcript-row-gap: 0px;
|
|
281
|
+
--wd-transcript-max-width: 48rem;
|
|
282
|
+
|
|
270
283
|
/* Stock-name bridge → raw custom properties (shadcn idiom compatibility). */
|
|
271
284
|
--primary: var(--accent);
|
|
272
285
|
--primary-foreground: var(--accent-fg);
|
|
@@ -611,3 +624,150 @@
|
|
|
611
624
|
transition: none !important;
|
|
612
625
|
}
|
|
613
626
|
}
|
|
627
|
+
|
|
628
|
+
/* ── Sticky prompt (cards variant) ─────────────────────────────────────────
|
|
629
|
+
*
|
|
630
|
+
* The same lane/head/sentinel mechanism as the terminal theme's pinned prompt
|
|
631
|
+
* (terminal.css scopes its rules under `[data-terminal]`; these key on the
|
|
632
|
+
* lane's own `data-sticky-lane='cards'` value) — but the head's CONTENT is
|
|
633
|
+
* different in kind, and that is the whole design. The terminal head is the
|
|
634
|
+
* row rendered again, clipped to one line — exact, because that theme is a
|
|
635
|
+
* monospace grid. A proportional message card clipped by height is a sliced
|
|
636
|
+
* bubble, so the cards head never renders the Message component at all:
|
|
637
|
+
* `StickyPromptLane` (Transcript.tsx) hands it the prompt as one run of plain
|
|
638
|
+
* text, and these rules only draw a bar. Nothing to flatten, nothing to
|
|
639
|
+
* un-style, nothing to `!important` over.
|
|
640
|
+
*
|
|
641
|
+
* Alignment is by construction, not restatement: the lane sits inside
|
|
642
|
+
* ConversationContent's centred, padded column (`max-w` + `mx-auto` + `px-4`)
|
|
643
|
+
* and spans it edge to edge, so the bar's text starts on the same left edge
|
|
644
|
+
* as the response paragraphs and the ellipsis lands on the column's right
|
|
645
|
+
* edge — no max-width or inline padding repeated here. The bar carries no
|
|
646
|
+
* gap class either (the measured row keeps it): it is not an overlay of the
|
|
647
|
+
* row's first line, so it has no in-flow y to match, and the 1st prompt (no
|
|
648
|
+
* gap) and every later one (pt-4/pt-2) share these rules unchanged. */
|
|
649
|
+
[data-slot='transcript-rows'] > [data-sticky-lane='cards'] {
|
|
650
|
+
z-index: 3;
|
|
651
|
+
pointer-events: none;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/* The head's own strip: the full lane, out of flow — see terminal.css for why
|
|
655
|
+
sticky confinement wants the border box, not a cancelled margin box. */
|
|
656
|
+
[data-sticky-lane='cards'] > [data-sticky-headlane] {
|
|
657
|
+
position: absolute;
|
|
658
|
+
inset: 0;
|
|
659
|
+
pointer-events: none;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* The bar: ~28px, one line centred by its own line-height, truncated at the
|
|
663
|
+
column's right edge, on a frosted ground so the rows passing beneath read
|
|
664
|
+
as beneath. Hidden until actually stuck (`data-stuck`, set by the lane's
|
|
665
|
+
sentinel observer): unstuck it sits at the lane's top, over the real
|
|
666
|
+
bubble. */
|
|
667
|
+
[data-sticky-lane='cards'] > [data-sticky-headlane] > [data-sticky-head] {
|
|
668
|
+
position: sticky;
|
|
669
|
+
top: 0;
|
|
670
|
+
height: 1.75rem;
|
|
671
|
+
line-height: 1.75rem;
|
|
672
|
+
overflow: hidden;
|
|
673
|
+
white-space: nowrap;
|
|
674
|
+
text-overflow: ellipsis;
|
|
675
|
+
font-size: var(--text-body-sm);
|
|
676
|
+
color: var(--fg-2);
|
|
677
|
+
background: color-mix(in srgb, var(--bg) 88%, transparent);
|
|
678
|
+
backdrop-filter: blur(12px);
|
|
679
|
+
-webkit-backdrop-filter: blur(12px);
|
|
680
|
+
border-bottom: 1px solid var(--border);
|
|
681
|
+
user-select: none;
|
|
682
|
+
visibility: hidden;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
[data-sticky-lane='cards'] > [data-sticky-headlane] > [data-sticky-head][data-stuck] {
|
|
686
|
+
visibility: visible;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* ── Overview scrubber (cards variant) ──────────────────────────────────────
|
|
690
|
+
*
|
|
691
|
+
* The proportional rail (`components/agent/Scrubber.tsx`): same geometry as the
|
|
692
|
+
* terminal rail — 12px, two 6px lanes plus full width — and the same palette
|
|
693
|
+
* mapped onto the app tokens. It is an annotation rail, never a scrollbar: the
|
|
694
|
+
* container takes no pointer events, so the native scrollbar keeps working
|
|
695
|
+
* straight through the paint. */
|
|
696
|
+
.wd-scrubber {
|
|
697
|
+
position: absolute;
|
|
698
|
+
top: 0;
|
|
699
|
+
right: 0;
|
|
700
|
+
bottom: 0;
|
|
701
|
+
width: 12px;
|
|
702
|
+
z-index: 10;
|
|
703
|
+
pointer-events: none;
|
|
704
|
+
}
|
|
705
|
+
.wd-scrubber .wd-scrub-mark {
|
|
706
|
+
position: absolute;
|
|
707
|
+
min-height: 2px;
|
|
708
|
+
}
|
|
709
|
+
.wd-scrubber[data-interactive] .wd-scrub-mark {
|
|
710
|
+
pointer-events: auto;
|
|
711
|
+
cursor: pointer;
|
|
712
|
+
}
|
|
713
|
+
.wd-scrubber[data-interactive] .wd-scrub-mark:hover {
|
|
714
|
+
outline: 1px solid color-mix(in srgb, var(--fg-1) 40%, transparent);
|
|
715
|
+
}
|
|
716
|
+
.wd-scrubber .wd-scrub-mark[data-lane='l'] { left: 0; width: 6px; }
|
|
717
|
+
.wd-scrubber .wd-scrub-mark[data-lane='r'] { left: 6px; width: 6px; }
|
|
718
|
+
.wd-scrubber .wd-scrub-mark[data-lane='f'] { left: 0; width: 12px; }
|
|
719
|
+
.wd-scrubber .wd-scrub-mark[data-kind='user'] { background: var(--accent); }
|
|
720
|
+
.wd-scrubber .wd-scrub-mark[data-kind='subagent'] { background: var(--success); }
|
|
721
|
+
.wd-scrubber .wd-scrub-mark[data-kind='turn'] { background: var(--fg-3); }
|
|
722
|
+
.wd-scrubber .wd-scrub-mark[data-kind='turnFailed'] { background: var(--danger); }
|
|
723
|
+
.wd-scrubber .wd-scrub-mark[data-kind='error'] { background: var(--danger); }
|
|
724
|
+
.wd-scrubber .wd-scrub-mark[data-kind='toolFailed'] {
|
|
725
|
+
background: color-mix(in srgb, var(--danger) 55%, transparent);
|
|
726
|
+
}
|
|
727
|
+
.wd-scrubber .wd-scrub-mark[data-kind='bookmark'] { background: var(--wd-scrub-bookmark); }
|
|
728
|
+
.wd-scrubber .wd-scrub-mark[data-kind='approval'] {
|
|
729
|
+
background: var(--warning);
|
|
730
|
+
animation: wd-scrub-pulse 1.2s infinite;
|
|
731
|
+
}
|
|
732
|
+
.wd-scrubber .wd-scrub-mark[data-kind='recap'] {
|
|
733
|
+
background: repeating-linear-gradient(to right, var(--fg-4) 0 3px, transparent 3px 6px)
|
|
734
|
+
left center / 100% 1px no-repeat;
|
|
735
|
+
}
|
|
736
|
+
@keyframes wd-scrub-pulse {
|
|
737
|
+
50% { opacity: 0.3; }
|
|
738
|
+
}
|
|
739
|
+
@media (prefers-reduced-motion: reduce) {
|
|
740
|
+
.wd-scrubber .wd-scrub-mark[data-kind='approval'] {
|
|
741
|
+
animation: none;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.wd-scrubber .wd-scrub-peek {
|
|
746
|
+
position: absolute;
|
|
747
|
+
right: calc(100% + 6px);
|
|
748
|
+
width: 320px;
|
|
749
|
+
max-width: 60vw;
|
|
750
|
+
display: flex;
|
|
751
|
+
flex-direction: column;
|
|
752
|
+
gap: 2px;
|
|
753
|
+
padding: 8px 10px;
|
|
754
|
+
border: 1px solid var(--border-strong);
|
|
755
|
+
border-radius: var(--radius-md, 6px);
|
|
756
|
+
background: var(--bg);
|
|
757
|
+
box-shadow: var(--shadow-md);
|
|
758
|
+
font-family: var(--cw-font-sans);
|
|
759
|
+
font-size: 12px;
|
|
760
|
+
line-height: 1.45;
|
|
761
|
+
color: var(--fg-1);
|
|
762
|
+
pointer-events: none;
|
|
763
|
+
z-index: 11;
|
|
764
|
+
}
|
|
765
|
+
.wd-scrubber .wd-scrub-peek [data-tone='muted'] { color: var(--fg-3); }
|
|
766
|
+
.wd-scrubber .wd-scrub-peek [data-tone='danger'] { color: var(--danger); }
|
|
767
|
+
.wd-scrubber .wd-scrub-ex {
|
|
768
|
+
display: -webkit-box;
|
|
769
|
+
-webkit-line-clamp: 4;
|
|
770
|
+
-webkit-box-orient: vertical;
|
|
771
|
+
overflow: hidden;
|
|
772
|
+
overflow-wrap: anywhere;
|
|
773
|
+
}
|