@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.
- package/CHANGELOG.md +12 -0
- package/dist/ai/code-mode/code-mode.svelte +201 -0
- package/dist/ai/code-mode/code-mode.svelte.d.ts +15 -0
- package/dist/ai/code-mode/index.d.ts +2 -0
- package/dist/ai/code-mode/index.js +1 -0
- package/dist/ai/code-mode/types.d.ts +26 -0
- package/dist/ai/code-mode/types.js +1 -0
- package/dist/ai/confirmation/batch.d.ts +21 -0
- package/dist/ai/confirmation/batch.js +1 -0
- package/dist/ai/confirmation/confirmation-batch.svelte +190 -0
- package/dist/ai/confirmation/confirmation-batch.svelte.d.ts +17 -0
- package/dist/ai/confirmation/index.d.ts +2 -0
- package/dist/ai/confirmation/index.js +1 -0
- package/dist/ai/conversation/conversation-scroll-button.svelte +2 -2
- package/dist/ai/conversations/conversations-item.svelte +39 -0
- package/dist/ai/conversations/conversations-item.svelte.d.ts +12 -0
- package/dist/ai/conversations/conversations.svelte.d.ts +6 -0
- package/dist/ai/index.d.ts +3 -1
- package/dist/ai/index.js +1 -0
- package/dist/ai/model-selector/index.d.ts +1 -0
- package/dist/ai/model-selector/model-selector.svelte +65 -31
- package/dist/ai/model-selector/model-selector.svelte.d.ts +24 -7
- package/dist/ai/queue/index.d.ts +1 -1
- package/dist/ai/queue/queue-item.svelte +63 -6
- package/dist/ai/queue/queue-item.svelte.d.ts +12 -3
- package/dist/ai/queue/queue.svelte +33 -9
- package/dist/ai/queue/queue.svelte.d.ts +6 -2
- package/dist/ai/tool/index.d.ts +1 -1
- package/dist/ai/tool/index.js +1 -1
- package/dist/ai/tool/tool-chip.svelte +2 -12
- package/dist/ai/tool/tool-chip.svelte.d.ts +1 -1
- package/dist/ai/tool/tool.svelte +46 -16
- package/dist/ai/tool/tool.svelte.d.ts +11 -2
- package/dist/era-ui.css +1 -1
- package/dist/ui/file-diff/file-diff.svelte +243 -0
- package/dist/ui/file-diff/file-diff.svelte.d.ts +16 -0
- package/dist/ui/file-diff/fixture.svelte +76 -0
- package/dist/ui/file-diff/fixture.svelte.d.ts +3 -0
- package/dist/ui/file-diff/index.d.ts +2 -0
- package/dist/ui/file-diff/index.js +1 -0
- package/dist/ui/file-diff/types.d.ts +31 -0
- package/dist/ui/file-diff/types.js +1 -0
- package/dist/ui/tabs/tabs.svelte +7 -2
- package/dist/ui/tabs/tabs.svelte.d.ts +1 -1
- package/dist/ui/terminal/fixture.svelte +57 -0
- package/dist/ui/terminal/fixture.svelte.d.ts +3 -0
- package/dist/ui/terminal/index.d.ts +2 -0
- package/dist/ui/terminal/index.js +1 -0
- package/dist/ui/terminal/terminal.svelte +91 -0
- package/dist/ui/terminal/terminal.svelte.d.ts +18 -0
- package/dist/ui/terminal/types.d.ts +26 -0
- package/dist/ui/terminal/types.js +1 -0
- package/package.json +16 -1
- package/skills/era-ui/references/releases/34.md +12 -0
- 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 @@
|
|
|
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';
|
|
@@ -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 {
|