@signal9/era-ui 34.2.9 → 34.4.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 (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/ai/code-mode/code-mode.svelte +201 -0
  3. package/dist/ai/code-mode/code-mode.svelte.d.ts +15 -0
  4. package/dist/ai/code-mode/index.d.ts +2 -0
  5. package/dist/ai/code-mode/index.js +1 -0
  6. package/dist/ai/code-mode/types.d.ts +26 -0
  7. package/dist/ai/code-mode/types.js +1 -0
  8. package/dist/ai/confirmation/batch.d.ts +21 -0
  9. package/dist/ai/confirmation/batch.js +1 -0
  10. package/dist/ai/confirmation/confirmation-batch.svelte +190 -0
  11. package/dist/ai/confirmation/confirmation-batch.svelte.d.ts +17 -0
  12. package/dist/ai/confirmation/index.d.ts +2 -0
  13. package/dist/ai/confirmation/index.js +1 -0
  14. package/dist/ai/conversation/conversation-scroll-button.svelte +2 -2
  15. package/dist/ai/conversations/conversations-item.svelte +39 -0
  16. package/dist/ai/conversations/conversations-item.svelte.d.ts +12 -0
  17. package/dist/ai/conversations/conversations.svelte.d.ts +6 -0
  18. package/dist/ai/index.d.ts +3 -1
  19. package/dist/ai/index.js +1 -0
  20. package/dist/ai/model-selector/index.d.ts +1 -0
  21. package/dist/ai/model-selector/model-selector.svelte +65 -31
  22. package/dist/ai/model-selector/model-selector.svelte.d.ts +24 -7
  23. package/dist/ai/queue/index.d.ts +1 -1
  24. package/dist/ai/queue/queue-item.svelte +63 -6
  25. package/dist/ai/queue/queue-item.svelte.d.ts +12 -3
  26. package/dist/ai/queue/queue.svelte +33 -9
  27. package/dist/ai/queue/queue.svelte.d.ts +6 -2
  28. package/dist/ai/tool/index.d.ts +1 -1
  29. package/dist/ai/tool/index.js +1 -1
  30. package/dist/ai/tool/tool-chip.svelte +2 -12
  31. package/dist/ai/tool/tool-chip.svelte.d.ts +1 -1
  32. package/dist/ai/tool/tool.svelte +46 -16
  33. package/dist/ai/tool/tool.svelte.d.ts +11 -2
  34. package/dist/era-ui.css +1 -1
  35. package/dist/ui/file-diff/file-diff.svelte +243 -0
  36. package/dist/ui/file-diff/file-diff.svelte.d.ts +16 -0
  37. package/dist/ui/file-diff/fixture.svelte +76 -0
  38. package/dist/ui/file-diff/fixture.svelte.d.ts +3 -0
  39. package/dist/ui/file-diff/index.d.ts +2 -0
  40. package/dist/ui/file-diff/index.js +1 -0
  41. package/dist/ui/file-diff/types.d.ts +31 -0
  42. package/dist/ui/file-diff/types.js +1 -0
  43. package/dist/ui/tabs/tabs.svelte +7 -2
  44. package/dist/ui/tabs/tabs.svelte.d.ts +1 -1
  45. package/dist/ui/terminal/fixture.svelte +57 -0
  46. package/dist/ui/terminal/fixture.svelte.d.ts +3 -0
  47. package/dist/ui/terminal/index.d.ts +2 -0
  48. package/dist/ui/terminal/index.js +1 -0
  49. package/dist/ui/terminal/terminal.svelte +91 -0
  50. package/dist/ui/terminal/terminal.svelte.d.ts +18 -0
  51. package/dist/ui/terminal/types.d.ts +26 -0
  52. package/dist/ui/terminal/types.js +1 -0
  53. package/package.json +16 -1
  54. package/skills/era-ui/references/releases/34.md +12 -0
  55. package/skills/era-ui/references/releases/index.md +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [34.4.0](https://github.com/sig-nine/era-ui/compare/v34.3.0...v34.4.0) (2026-08-25)
2
+
3
+ ### Features
4
+
5
+ * add Navi execution primitives ([540c97b](https://github.com/sig-nine/era-ui/commit/540c97bc0a4f45b106dd7bb4228810d2f17c9db6))
6
+
7
+ ## [34.3.0](https://github.com/sig-nine/era-ui/compare/v34.2.9...v34.3.0) (2026-08-25)
8
+
9
+ ### Features
10
+
11
+ * **ai:** generalize durable provider primitives ([808f9e3](https://github.com/sig-nine/era-ui/commit/808f9e3ff5d0446cc80d9e020c9fdf1259ce934e))
12
+
1
13
  ## [34.2.9](https://github.com/sig-nine/era-ui/compare/v34.2.8...v34.2.9) (2026-08-25)
2
14
 
3
15
  ### Bug Fixes
@@ -0,0 +1,201 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { Badge } from '../../ui/badge/index.js';
5
+ import { Bar } from '../../ui/bar/index.js';
6
+ import { Button } from '../../ui/button/index.js';
7
+ import { CodeBlock } from '../../ui/code-block/index.js';
8
+ import * as Tabs from '../../ui/tabs/index.js';
9
+ import { cn } from '../../utils/index.js';
10
+ import * as RunStatus from '../run-status/index.js';
11
+ import * as StructuredValue from '../structured-value/index.js';
12
+ import type {
13
+ CodeModeCallState,
14
+ CodeModeExternalCall,
15
+ CodeModeExecution,
16
+ CodeModeLog,
17
+ CodeModeView
18
+ } from './types.js';
19
+
20
+ interface Props extends HTMLAttributes<HTMLDivElement>, CodeModeExecution {
21
+ ref?: HTMLDivElement | null;
22
+ activeView?: CodeModeView;
23
+ label?: string;
24
+ oncancel?: () => void;
25
+ call?: Snippet<[CodeModeExternalCall]>;
26
+ resultView?: Snippet<[unknown]>;
27
+ empty?: Snippet<[CodeModeView]>;
28
+ }
29
+
30
+ let {
31
+ ref = $bindable(null),
32
+ status,
33
+ source,
34
+ language = 'javascript',
35
+ logs = [],
36
+ externalCalls = [],
37
+ result,
38
+ error,
39
+ activeView = $bindable('source'),
40
+ label = 'Code Mode execution',
41
+ oncancel,
42
+ call,
43
+ resultView,
44
+ empty,
45
+ class: className,
46
+ ...restProps
47
+ }: Props = $props();
48
+
49
+ const latest = $derived(logs.at(-1));
50
+ const completedCalls = $derived(
51
+ externalCalls.filter((externalCall) => externalCall.state === 'complete').length
52
+ );
53
+ const failedCalls = $derived(
54
+ externalCalls.filter((externalCall) => externalCall.state === 'failed').length
55
+ );
56
+
57
+ const callTone: Record<CodeModeCallState, 'default' | 'accent' | 'success' | 'destructive'> = {
58
+ queued: 'default',
59
+ running: 'accent',
60
+ complete: 'success',
61
+ failed: 'destructive',
62
+ cancelled: 'default'
63
+ };
64
+
65
+ function logClass(log: CodeModeLog): string {
66
+ return (
67
+ {
68
+ default: 'text-fg',
69
+ muted: 'text-muted',
70
+ success: 'text-success',
71
+ warning: 'text-warning',
72
+ destructive: 'text-destructive'
73
+ } as const
74
+ )[log.tone ?? 'default'];
75
+ }
76
+ </script>
77
+
78
+ <div
79
+ bind:this={ref}
80
+ aria-label={label}
81
+ class={cn(
82
+ 'flex min-h-0 min-w-0 flex-col overflow-hidden rounded-bar bg-well shadow-well',
83
+ className
84
+ )}
85
+ {...restProps}
86
+ >
87
+ <RunStatus.Root
88
+ {status}
89
+ tools={externalCalls.length
90
+ ? {
91
+ complete: completedCalls,
92
+ total: externalCalls.length,
93
+ running: externalCalls.filter((externalCall) => externalCall.state === 'running').length,
94
+ failed: failedCalls
95
+ }
96
+ : undefined}
97
+ latest={latest ? { tone: latest.tone ?? 'default', text: latest.text } : undefined}
98
+ {oncancel}
99
+ class="rounded-b-none shadow-none"
100
+ />
101
+
102
+ <Tabs.Root bind:value={activeView} class="flex min-h-0 flex-1 flex-col">
103
+ <Tabs.List>
104
+ <Bar size="control" content="chip" divider class="overflow-x-auto rounded-none">
105
+ {#each ['source', 'console', 'calls', 'result'] as view (view)}
106
+ <Tabs.Trigger value={view}>
107
+ {#snippet child({ props })}
108
+ <Button
109
+ {...props}
110
+ size="chip"
111
+ class="data-[state=active]:bg-highlight data-[state=active]:text-bright"
112
+ >
113
+ {view}{view === 'console' && logs.length ? ` ${logs.length}` : ''}{view ===
114
+ 'calls' && externalCalls.length
115
+ ? ` ${externalCalls.length}`
116
+ : ''}
117
+ </Button>
118
+ {/snippet}
119
+ </Tabs.Trigger>
120
+ {/each}
121
+ </Bar>
122
+ </Tabs.List>
123
+
124
+ <Tabs.Content value="source" class="min-h-0 overflow-auto p-panel">
125
+ {#if source}
126
+ <CodeBlock code={source} data-language={language} />
127
+ {:else if empty}
128
+ {@render empty('source')}
129
+ {:else}
130
+ <p class="text-body text-muted">No source</p>
131
+ {/if}
132
+ </Tabs.Content>
133
+
134
+ <Tabs.Content value="console" class="min-h-0 overflow-auto p-panel">
135
+ {#if logs.length}
136
+ <div role="log" aria-live="polite" class="flex min-w-0 flex-col font-mono text-body">
137
+ {#each logs as log (log.id)}
138
+ <div class={cn('min-h-control whitespace-pre-wrap', logClass(log))}>{log.text}</div>
139
+ {/each}
140
+ </div>
141
+ {:else if empty}
142
+ {@render empty('console')}
143
+ {:else}
144
+ <p class="text-body text-muted">No console output</p>
145
+ {/if}
146
+ </Tabs.Content>
147
+
148
+ <Tabs.Content value="calls" class="min-h-0 overflow-auto p-panel">
149
+ {#if externalCalls.length}
150
+ <div class="flex min-w-0 flex-col gap-panel">
151
+ {#each externalCalls as externalCall (externalCall.id)}
152
+ {#if call}
153
+ {@render call(externalCall)}
154
+ {:else}
155
+ <article
156
+ class="flex min-w-0 flex-col gap-gutter border-b border-divider-faded pb-panel"
157
+ >
158
+ <header class="flex h-control items-center gap-gutter">
159
+ <code class="min-w-0 flex-1 truncate text-bright">{externalCall.tool}</code>
160
+ {#if externalCall.durationMs !== undefined}
161
+ <span class="text-muted tabular-nums">{externalCall.durationMs}ms</span>
162
+ {/if}
163
+ <Badge tone={callTone[externalCall.state]}>{externalCall.state}</Badge>
164
+ </header>
165
+ {#if externalCall.input !== undefined}
166
+ <StructuredValue.Root value={externalCall.input} />
167
+ {/if}
168
+ {#if externalCall.output !== undefined}
169
+ <StructuredValue.Root value={externalCall.output} />
170
+ {/if}
171
+ {#if externalCall.error}
172
+ <p class="text-body text-destructive">{externalCall.error}</p>
173
+ {/if}
174
+ </article>
175
+ {/if}
176
+ {/each}
177
+ </div>
178
+ {:else if empty}
179
+ {@render empty('calls')}
180
+ {:else}
181
+ <p class="text-body text-muted">No external calls</p>
182
+ {/if}
183
+ </Tabs.Content>
184
+
185
+ <Tabs.Content value="result" class="min-h-0 overflow-auto p-panel">
186
+ {#if error}
187
+ <p class="font-mono text-body whitespace-pre-wrap text-destructive">{error}</p>
188
+ {:else if result !== undefined}
189
+ {#if resultView}
190
+ {@render resultView(result)}
191
+ {:else}
192
+ <StructuredValue.Root value={result} />
193
+ {/if}
194
+ {:else if empty}
195
+ {@render empty('result')}
196
+ {:else}
197
+ <p class="text-body text-muted">No result yet</p>
198
+ {/if}
199
+ </Tabs.Content>
200
+ </Tabs.Root>
201
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { CodeModeExternalCall, CodeModeExecution, CodeModeView } from './types.js';
4
+ interface Props extends HTMLAttributes<HTMLDivElement>, CodeModeExecution {
5
+ ref?: HTMLDivElement | null;
6
+ activeView?: CodeModeView;
7
+ label?: string;
8
+ oncancel?: () => void;
9
+ call?: Snippet<[CodeModeExternalCall]>;
10
+ resultView?: Snippet<[unknown]>;
11
+ empty?: Snippet<[CodeModeView]>;
12
+ }
13
+ declare const CodeMode: import("svelte").Component<Props, {}, "ref" | "activeView">;
14
+ type CodeMode = ReturnType<typeof CodeMode>;
15
+ export default CodeMode;
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './code-mode.svelte';
2
+ export type { CodeModeCallState, CodeModeExecution, CodeModeExternalCall, CodeModeLog, CodeModeView } from './types.js';
@@ -0,0 +1 @@
1
+ export { default as Root } from './code-mode.svelte';
@@ -0,0 +1,26 @@
1
+ import type { LogTone, RunState } from '../run-status/index.js';
2
+ export type CodeModeView = 'source' | 'console' | 'calls' | 'result';
3
+ export interface CodeModeLog {
4
+ id: string | number;
5
+ text: string;
6
+ tone?: LogTone;
7
+ }
8
+ export type CodeModeCallState = 'queued' | 'running' | 'complete' | 'failed' | 'cancelled';
9
+ export interface CodeModeExternalCall {
10
+ id: string;
11
+ tool: string;
12
+ state: CodeModeCallState;
13
+ input?: unknown;
14
+ output?: unknown;
15
+ error?: string;
16
+ durationMs?: number;
17
+ }
18
+ export interface CodeModeExecution {
19
+ status: RunState;
20
+ source: string;
21
+ language?: string;
22
+ logs?: readonly CodeModeLog[];
23
+ externalCalls?: readonly CodeModeExternalCall[];
24
+ result?: unknown;
25
+ error?: string;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { ApprovalController } from './approval.svelte.js';
2
+ export interface ApprovalScope {
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ }
7
+ export interface BatchedApprovalRequest {
8
+ id: string;
9
+ title: string;
10
+ description?: string;
11
+ capability?: string;
12
+ /** Omit to permit every scope offered by the enclosing panel. */
13
+ scopeIds?: readonly string[];
14
+ controller: ApprovalController;
15
+ }
16
+ export interface BatchedApprovalDecision {
17
+ decision: 'approved' | 'denied';
18
+ scopeId: string;
19
+ requestIds: string[];
20
+ overrides: Record<string, Record<string, unknown>>;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,190 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { SvelteSet } from 'svelte/reactivity';
4
+ import * as Checkbox from '../../ui/checkbox/index.js';
5
+ import { Button } from '../../ui/button/index.js';
6
+ import { Label } from '../../ui/label/index.js';
7
+ import { cn } from '../../utils/index.js';
8
+ import Args from './confirmation-args.svelte';
9
+ import type { ApprovalScope, BatchedApprovalDecision, BatchedApprovalRequest } from './batch.js';
10
+
11
+ interface Props extends HTMLAttributes<HTMLDivElement> {
12
+ ref?: HTMLDivElement | null;
13
+ requests: readonly BatchedApprovalRequest[];
14
+ scopes: readonly ApprovalScope[];
15
+ selectedIds?: string[];
16
+ scopeId?: string | null;
17
+ label?: string;
18
+ busy?: boolean;
19
+ onapprove?: (decision: BatchedApprovalDecision) => void | Promise<void>;
20
+ ondeny?: (decision: BatchedApprovalDecision) => void | Promise<void>;
21
+ ondecision?: (decision: BatchedApprovalDecision) => void | Promise<void>;
22
+ }
23
+
24
+ let {
25
+ ref = $bindable(null),
26
+ requests,
27
+ scopes,
28
+ selectedIds = $bindable([]),
29
+ scopeId = $bindable(null),
30
+ label = 'Requested actions',
31
+ busy = false,
32
+ onapprove,
33
+ ondeny,
34
+ ondecision,
35
+ class: className,
36
+ ...restProps
37
+ }: Props = $props();
38
+
39
+ const selected = $derived(new SvelteSet(selectedIds));
40
+ const selectedRequests = $derived(requests.filter((request) => selected.has(request.id)));
41
+ const allowedScopes = $derived(
42
+ scopes.filter((scope) =>
43
+ selectedRequests.every((request) => !request.scopeIds || request.scopeIds.includes(scope.id))
44
+ )
45
+ );
46
+ const effectiveScopeId = $derived(
47
+ allowedScopes.some((scope) => scope.id === scopeId) ? scopeId : (allowedScopes[0]?.id ?? null)
48
+ );
49
+ const allSelected = $derived(requests.length > 0 && selectedRequests.length === requests.length);
50
+ const someSelected = $derived(selectedRequests.length > 0 && !allSelected);
51
+ const canDecide = $derived(selectedRequests.length > 0 && effectiveScopeId !== null);
52
+ const canApprove = $derived(
53
+ selectedRequests.length > 0 &&
54
+ effectiveScopeId !== null &&
55
+ selectedRequests.every((request) => request.controller.valid)
56
+ );
57
+
58
+ function toggleRequest(requestId: string, checked: boolean): void {
59
+ const next = new SvelteSet(selectedIds);
60
+ if (checked) next.add(requestId);
61
+ else next.delete(requestId);
62
+ selectedIds = [...next];
63
+ }
64
+
65
+ function toggleAll(checked: boolean): void {
66
+ selectedIds = checked ? requests.map((request) => request.id) : [];
67
+ }
68
+
69
+ function chooseScope(nextScopeId: string): void {
70
+ scopeId = nextScopeId;
71
+ }
72
+
73
+ function decision(kind: BatchedApprovalDecision['decision']): BatchedApprovalDecision | null {
74
+ if (!canDecide || (kind === 'approved' && !canApprove) || !effectiveScopeId) return null;
75
+ return {
76
+ decision: kind,
77
+ scopeId: effectiveScopeId,
78
+ requestIds: selectedRequests.map((request) => request.id),
79
+ overrides: Object.fromEntries(
80
+ selectedRequests.map((request) => [request.id, request.controller.diff()])
81
+ )
82
+ };
83
+ }
84
+
85
+ async function submit(kind: BatchedApprovalDecision['decision']): Promise<void> {
86
+ const payload = decision(kind);
87
+ if (!payload) return;
88
+ await ondecision?.(payload);
89
+ if (kind === 'approved') await onapprove?.(payload);
90
+ else await ondeny?.(payload);
91
+ }
92
+ </script>
93
+
94
+ <div
95
+ bind:this={ref}
96
+ class={cn('flex min-w-0 flex-col gap-panel rounded-bar bg-well p-panel shadow-well', className)}
97
+ {...restProps}
98
+ >
99
+ <fieldset disabled={busy} class="flex min-w-0 flex-col gap-panel">
100
+ <legend class="sr-only">{label}</legend>
101
+ <div class="flex h-control items-center gap-gutter border-b border-divider-faded">
102
+ <Label
103
+ class="flex min-w-0 flex-1 cursor-pointer items-center gap-gutter font-medium text-bright"
104
+ >
105
+ <Checkbox.Root
106
+ checked={allSelected}
107
+ indeterminate={someSelected}
108
+ onCheckedChange={toggleAll}
109
+ aria-label="Select all actions"
110
+ />
111
+ <span class="min-w-0 flex-1 truncate">{label}</span>
112
+ </Label>
113
+ <span class="text-body text-muted tabular-nums"
114
+ >{selectedRequests.length}/{requests.length}</span
115
+ >
116
+ </div>
117
+
118
+ <div class="flex min-w-0 flex-col gap-panel">
119
+ {#each requests as request (request.id)}
120
+ <article
121
+ class={cn(
122
+ 'flex min-w-0 flex-col gap-gutter border-b border-divider-faded pb-panel',
123
+ selected.has(request.id) && 'bg-highlight px-panel'
124
+ )}
125
+ >
126
+ <Label class="flex min-h-control cursor-pointer items-center gap-gutter">
127
+ <Checkbox.Root
128
+ checked={selected.has(request.id)}
129
+ onCheckedChange={(checked) => toggleRequest(request.id, checked)}
130
+ aria-label={`Select ${request.title}`}
131
+ />
132
+ <span class="flex min-w-0 flex-1 flex-col gap-inset-pill">
133
+ <span class="truncate font-medium text-bright">{request.title}</span>
134
+ {#if request.description}
135
+ <span class="text-body text-muted">{request.description}</span>
136
+ {/if}
137
+ </span>
138
+ {#if request.capability}
139
+ <code class="shrink-0 text-body text-muted">{request.capability}</code>
140
+ {/if}
141
+ </Label>
142
+ {#if selected.has(request.id)}
143
+ <Args
144
+ controller={request.controller}
145
+ class="ps-[calc(var(--era-h-icon)+var(--era-gap))]"
146
+ />
147
+ {/if}
148
+ </article>
149
+ {/each}
150
+ </div>
151
+
152
+ <div class="flex min-w-0 flex-col gap-gutter">
153
+ <span class="text-body text-muted">Approval scope</span>
154
+ <div role="radiogroup" aria-label="Approval scope" class="flex flex-wrap gap-gutter">
155
+ {#each scopes as scope (scope.id)}
156
+ {@const allowed = allowedScopes.some((candidate) => candidate.id === scope.id)}
157
+ <Button
158
+ type="button"
159
+ size="chip"
160
+ role="radio"
161
+ aria-checked={scope.id === effectiveScopeId}
162
+ active={scope.id === effectiveScopeId}
163
+ disabled={!allowed}
164
+ title={scope.description}
165
+ onclick={() => chooseScope(scope.id)}
166
+ >
167
+ {scope.label}
168
+ </Button>
169
+ {/each}
170
+ </div>
171
+ </div>
172
+
173
+ <div class="flex flex-wrap items-center justify-end gap-gutter">
174
+ <Button
175
+ type="button"
176
+ tone="destructive"
177
+ disabled={!canDecide}
178
+ {busy}
179
+ onclick={() => void submit('denied')}>Deny selected</Button
180
+ >
181
+ <Button
182
+ type="button"
183
+ tone="success"
184
+ disabled={!canApprove}
185
+ {busy}
186
+ onclick={() => void submit('approved')}>Approve selected</Button
187
+ >
188
+ </div>
189
+ </fieldset>
190
+ </div>
@@ -0,0 +1,17 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { ApprovalScope, BatchedApprovalDecision, BatchedApprovalRequest } from './batch.js';
3
+ interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ ref?: HTMLDivElement | null;
5
+ requests: readonly BatchedApprovalRequest[];
6
+ scopes: readonly ApprovalScope[];
7
+ selectedIds?: string[];
8
+ scopeId?: string | null;
9
+ label?: string;
10
+ busy?: boolean;
11
+ onapprove?: (decision: BatchedApprovalDecision) => void | Promise<void>;
12
+ ondeny?: (decision: BatchedApprovalDecision) => void | Promise<void>;
13
+ ondecision?: (decision: BatchedApprovalDecision) => void | Promise<void>;
14
+ }
15
+ declare const ConfirmationBatch: import("svelte").Component<Props, {}, "ref" | "selectedIds" | "scopeId">;
16
+ type ConfirmationBatch = ReturnType<typeof ConfirmationBatch>;
17
+ export default ConfirmationBatch;
@@ -1,4 +1,6 @@
1
1
  export { default as Root, type ApprovalState } from './confirmation.svelte';
2
2
  export { default as Actions } from './confirmation-actions.svelte';
3
3
  export { default as Args } from './confirmation-args.svelte';
4
+ export { default as Batch } from './confirmation-batch.svelte';
5
+ export type { ApprovalScope, BatchedApprovalDecision, BatchedApprovalRequest } from './batch.js';
4
6
  export { ApprovalController, setApprovalContext, getApprovalContext, type FieldKind } from './approval.svelte.js';
@@ -1,4 +1,5 @@
1
1
  export { default as Root } from './confirmation.svelte';
2
2
  export { default as Actions } from './confirmation-actions.svelte';
3
3
  export { default as Args } from './confirmation-args.svelte';
4
+ export { default as Batch } from './confirmation-batch.svelte';
4
5
  export { ApprovalController, setApprovalContext, getApprovalContext } from './approval.svelte.js';
@@ -16,9 +16,9 @@
16
16
  >
17
17
  <Button
18
18
  icon
19
- size="control"
19
+ size="pill"
20
20
  aria-label="Scroll to bottom"
21
- class="rounded-full shadow-lg"
21
+ class="shadow-lg"
22
22
  onclick={() => stick.repin()}
23
23
  >
24
24
  <ArrowDown class="size-icon" />
@@ -13,10 +13,16 @@
13
13
 
14
14
  let {
15
15
  title = $bindable(''),
16
+ id,
16
17
  starred = $bindable(false),
17
18
  status,
19
+ childStatus,
20
+ role,
21
+ depth = 0,
22
+ rootActive = false,
18
23
  active = false,
19
24
  leading,
25
+ actions,
20
26
  onSelect,
21
27
  onRename,
22
28
  onStar,
@@ -25,16 +31,28 @@
25
31
  class: className,
26
32
  ...restProps
27
33
  }: Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {
34
+ /** Stable consumer-owned identity, exposed as data-conversation-id. */
35
+ id?: string;
28
36
  /** Conversation label; double-click to rename in place. Bindable. */
29
37
  title?: string;
30
38
  /** Bindable star state; the rail's star button toggles it. */
31
39
  starred?: boolean;
32
40
  /** Drives the status dot tone; omit for no dot. */
33
41
  status?: ConversationStatus;
42
+ /** Separate delegated-child status mark. */
43
+ childStatus?: ConversationStatus;
44
+ /** Optional visible role label. */
45
+ role?: string;
46
+ /** Nesting depth; indentation follows the density axis. */
47
+ depth?: number;
48
+ /** Marks a root whose active child owns the current work. */
49
+ rootActive?: boolean;
34
50
  /** Highlighted as the open conversation. */
35
51
  active?: boolean;
36
52
  /** Overrides the leading icon (e.g. an expand chevron). */
37
53
  leading?: Snippet;
54
+ /** Consumer-owned actions, rendered ahead of built-in row actions. */
55
+ actions?: Snippet;
38
56
  /** Row activated (click / Enter on the title). */
39
57
  onSelect?: () => void;
40
58
  /** Fired after a non-empty rename commits, with the new title. */
@@ -127,7 +145,13 @@
127
145
  </script>
128
146
 
129
147
  <div
148
+ data-conversation-id={id}
130
149
  data-active={active || undefined}
150
+ data-root-active={rootActive || undefined}
151
+ data-depth={depth || undefined}
152
+ style:padding-inline-start={depth > 0
153
+ ? `calc(var(--era-inset-control) + ${Math.max(0, depth)} * var(--era-sp))`
154
+ : undefined}
131
155
  class={cn(
132
156
  // The trailing buttons are pill-tier icon buttons: h-pill box + h-icon glyph =
133
157
  // 2px of air around the glyph (the icon tier would hug it flush — density.css:
@@ -167,6 +191,20 @@
167
191
  aria-hidden="true"
168
192
  ></span>
169
193
  {/if}
194
+ {#if role}
195
+ <span
196
+ class="shrink-0 text-[0.625rem] era-text-trim-caps tracking-[0.12em] text-muted uppercase"
197
+ >
198
+ {role}
199
+ </span>
200
+ {/if}
201
+ {#if childStatus}
202
+ <span
203
+ class={cn('size-gutter shrink-0 rounded-full', DOT[childStatus].class)}
204
+ title={DOT[childStatus].label}
205
+ aria-label={`Child: ${DOT[childStatus].label}`}
206
+ ></span>
207
+ {/if}
170
208
 
171
209
  {#if editing}
172
210
  <!-- `bare`: the row is already the field's container, so the input brings no
@@ -201,6 +239,7 @@
201
239
  data-rail
202
240
  class="flex shrink-0 items-center gap-gutter opacity-0 group-hover/row:opacity-100 focus-within:opacity-100"
203
241
  >
242
+ {#if actions}{@render actions()}{/if}
204
243
  <Button
205
244
  icon
206
245
  size="pill"
@@ -2,16 +2,28 @@ import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import type { ConversationStatus } from './conversations.svelte.js';
4
4
  type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {
5
+ /** Stable consumer-owned identity, exposed as data-conversation-id. */
6
+ id?: string;
5
7
  /** Conversation label; double-click to rename in place. Bindable. */
6
8
  title?: string;
7
9
  /** Bindable star state; the rail's star button toggles it. */
8
10
  starred?: boolean;
9
11
  /** Drives the status dot tone; omit for no dot. */
10
12
  status?: ConversationStatus;
13
+ /** Separate delegated-child status mark. */
14
+ childStatus?: ConversationStatus;
15
+ /** Optional visible role label. */
16
+ role?: string;
17
+ /** Nesting depth; indentation follows the density axis. */
18
+ depth?: number;
19
+ /** Marks a root whose active child owns the current work. */
20
+ rootActive?: boolean;
11
21
  /** Highlighted as the open conversation. */
12
22
  active?: boolean;
13
23
  /** Overrides the leading icon (e.g. an expand chevron). */
14
24
  leading?: Snippet;
25
+ /** Consumer-owned actions, rendered ahead of built-in row actions. */
26
+ actions?: Snippet;
15
27
  /** Row activated (click / Enter on the title). */
16
28
  onSelect?: () => void;
17
29
  /** Fired after a non-empty rename commits, with the new title. */
@@ -10,6 +10,12 @@ export interface ConversationEntry {
10
10
  updatedAt: number;
11
11
  starred?: boolean;
12
12
  status?: ConversationStatus;
13
+ /** Optional parent identity for consumer-owned nested conversation trees. */
14
+ parentId?: string;
15
+ /** Reader-facing role label, for example "Research" or "Sub-agent". */
16
+ role?: string;
17
+ /** Distinct child execution state when a root row summarizes delegated work. */
18
+ childStatus?: ConversationStatus;
13
19
  }
14
20
  /** A rendered section: a bucket, its display label, and its member rows. */
15
21
  export interface ConversationGroup {