@workerdeck/ui 0.20.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-CnU_IJ3-.d.mts → SessionPanel-13l25ubU.d.mts} +55 -7
- package/build/{SessionPanel-CHktI2CF.mjs → SessionPanel-DgwjH4Ve.mjs} +642 -231
- package/build/SessionPanel-DgwjH4Ve.mjs.map +1 -0
- package/build/index.d.mts +309 -53
- package/build/index.mjs +534 -397
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +3547 -0
- package/build/workspace.d.mts +10 -1
- package/build/workspace.mjs +4 -2
- package/build/workspace.mjs.map +1 -1
- package/package.json +15 -7
- 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/SessionBrowser.tsx +113 -293
- package/src/components/agent/SessionItem.tsx +538 -0
- package/src/components/agent/SessionList.tsx +3 -1
- package/src/components/agent/SessionPanel.tsx +112 -21
- package/src/components/agent/SessionStatusIcon.tsx +43 -0
- package/src/components/agent/SessionSteps.tsx +118 -61
- package/src/components/agent/SessionWorkspace.tsx +11 -0
- 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/index.ts +5 -1
- package/src/styles/scoped.entry.css +16 -0
- package/src/styles/terminal.css +1 -1
- package/src/styles/theme.css +223 -8
- package/build/SessionPanel-CHktI2CF.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
|
|
package/src/index.ts
CHANGED
|
@@ -177,7 +177,11 @@ export {
|
|
|
177
177
|
rowShapeClass,
|
|
178
178
|
type SessionBrowserProps,
|
|
179
179
|
} from './components/agent/SessionBrowser.tsx'
|
|
180
|
-
|
|
180
|
+
// The session card itself — one component, every client. `SessionBrowser` is the
|
|
181
|
+
// dashboard's list *around* it; a host that wants only the card (the VS Code
|
|
182
|
+
// sidebar does, because the filtering chrome above it is native) takes this.
|
|
183
|
+
export { SessionItem, type SessionItemProps } from './components/agent/SessionItem.tsx'
|
|
184
|
+
export { SessionStatusIcon } from './components/agent/SessionStatusIcon.tsx'
|
|
181
185
|
// Lifted out of the VS Code sidebar once the dashboard grew a collapsed rail
|
|
182
186
|
// that needs the same glyph — two copies of a trademark set is one too many.
|
|
183
187
|
export {
|
|
@@ -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
|