@2lab.ai/ui 0.0.0-stage → 0.1.1

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 (52) hide show
  1. package/README.md +54 -2
  2. package/dist/agent-inbox.js +1061 -0
  3. package/dist/agent-inbox.js.map +1 -0
  4. package/dist/agent-queue.js +1610 -0
  5. package/dist/agent-queue.js.map +1 -0
  6. package/dist/agent-run.js +859 -0
  7. package/dist/agent-run.js.map +1 -0
  8. package/dist/ai-answer.js +687 -0
  9. package/dist/ai-answer.js.map +1 -0
  10. package/dist/components/agent-inbox/agent-inbox.d.ts +158 -0
  11. package/dist/components/agent-inbox/index.d.ts +2 -0
  12. package/dist/components/agent-inbox/styles.d.ts +6 -0
  13. package/dist/components/agent-queue/agent-queue.d.ts +41 -0
  14. package/dist/components/agent-queue/index.d.ts +6 -0
  15. package/dist/components/agent-queue/logic.d.ts +261 -0
  16. package/dist/components/agent-queue/strings.d.ts +473 -0
  17. package/dist/components/agent-queue/styles.d.ts +8 -0
  18. package/dist/components/agent-run/agent-run.d.ts +273 -0
  19. package/dist/components/agent-run/index.d.ts +2 -0
  20. package/dist/components/agent-run/styles.d.ts +6 -0
  21. package/dist/components/ai-answer/ai-answer.d.ts +92 -0
  22. package/dist/components/ai-answer/index.d.ts +2 -0
  23. package/dist/components/ai-answer/styles.d.ts +2 -0
  24. package/dist/components/context-window/context-window.d.ts +156 -0
  25. package/dist/components/context-window/index.d.ts +2 -0
  26. package/dist/components/context-window/styles.d.ts +2 -0
  27. package/dist/components/memory-panel/index.d.ts +2 -0
  28. package/dist/components/memory-panel/memory-panel.d.ts +101 -0
  29. package/dist/components/memory-panel/styles.d.ts +2 -0
  30. package/dist/components/model-picker/index.d.ts +2 -0
  31. package/dist/components/model-picker/model-picker.d.ts +136 -0
  32. package/dist/components/model-picker/morph.d.ts +9 -0
  33. package/dist/components/model-picker/styles.d.ts +6 -0
  34. package/dist/context-window.js +384 -0
  35. package/dist/context-window.js.map +1 -0
  36. package/dist/core/element.d.ts +39 -0
  37. package/dist/core/format.d.ts +14 -0
  38. package/dist/core/html.d.ts +10 -0
  39. package/dist/core/tokens.d.ts +14 -0
  40. package/dist/format-BBGWXPzk.js +51 -0
  41. package/dist/format-BBGWXPzk.js.map +1 -0
  42. package/dist/html-DZWnrdcg.js +167 -0
  43. package/dist/html-DZWnrdcg.js.map +1 -0
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.js +116 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/memory-panel.js +1255 -0
  48. package/dist/memory-panel.js.map +1 -0
  49. package/dist/model-picker.js +858 -0
  50. package/dist/model-picker.js.map +1 -0
  51. package/dist/tokens.css +4 -0
  52. package/package.json +75 -4
@@ -0,0 +1,273 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ /** The run's phase (the head chip). `running` and `waiting` are live: the elapsed clock ticks and Stop shows. */
3
+ export type AgentRunStatus = 'running' | 'waiting' | 'idle' | 'done' | 'failed' | 'stopped';
4
+ export type AgentRunStepStatus = 'pending' | 'running' | 'done' | 'failed' | 'skipped';
5
+ export type AgentRunDecision = 'approve' | 'deny' | 'explain' | 'approve_disable_rule';
6
+ export interface AgentRunStep {
7
+ id: string;
8
+ label: string;
9
+ status: AgentRunStepStatus;
10
+ /** Short reference shown before the label, e.g. `#T3`. */
11
+ ref?: string;
12
+ /** Second line: what the step is doing now, or why it failed. */
13
+ detail?: string;
14
+ /** Epoch ms. A running step ticks from here; a finished one shows endedAt − startedAt. */
15
+ startedAt?: number;
16
+ endedAt?: number;
17
+ /** Tool calls made while the step ran (chip; never shown on a pending step). */
18
+ toolCalls?: number;
19
+ }
20
+ export interface AgentRunRule {
21
+ id: string;
22
+ label: string;
23
+ description?: string;
24
+ }
25
+ /** One tool-permission request waiting on the person. */
26
+ export interface AgentRunApproval {
27
+ id: string;
28
+ /** Tool name (`Bash`, `gmail.send`). */
29
+ tool: string;
30
+ /** Card title. Default: `<tool> approval` / `<tool> 실행 승인`. */
31
+ title?: string;
32
+ /** One line under the title (e.g. the tool input's `description`). */
33
+ summary?: string;
34
+ /** The tool input as label → value rows. Non-string values are shown as pretty JSON. */
35
+ fields?: Array<[label: string, value: unknown]>;
36
+ /** The tool input as raw text, when it is not a key/value object (shown in a `<pre>`). */
37
+ raw?: string;
38
+ /** `raw` was cut by the host. */
39
+ truncated?: boolean;
40
+ /** Matched dangerous-command rules (drive the risk pill). */
41
+ rules?: AgentRunRule[];
42
+ /** Epoch ms the request was made (label line). */
43
+ requestedAt?: number;
44
+ /** Epoch ms the request expires (live countdown). */
45
+ expiresAt?: number;
46
+ /** Offer "Approve & disable rule". */
47
+ canDisableRule?: boolean;
48
+ /** Default `true`. `false` disables the answers; a string disables them and says why. */
49
+ canAnswer?: boolean | string;
50
+ /** The answer the host sent: the card becomes one resolved row. */
51
+ answered?: AgentRunDecision;
52
+ /** Why the last answer failed (the answers unlock again). */
53
+ error?: string;
54
+ }
55
+ export interface AgentRunChoice {
56
+ id: string;
57
+ label: string;
58
+ description?: string;
59
+ /** The recommended choice goes first, as the card's one primary (⌘/Ctrl+Enter). */
60
+ recommended?: boolean;
61
+ }
62
+ /** A single-choice question from the agent. */
63
+ export interface AgentRunQuestion {
64
+ id: string;
65
+ title: string;
66
+ body?: string;
67
+ choices: AgentRunChoice[];
68
+ canAnswer?: boolean | string;
69
+ /** Id of the choice the host sent: the card becomes one resolved row. */
70
+ answered?: string;
71
+ error?: string;
72
+ }
73
+ /** A details value: text, or a duration — live (`addMs + now − since`, ticking in place) when `since` is set, else `addMs`. */
74
+ export type AgentRunDetailValue = string | number | {
75
+ since?: number;
76
+ addMs?: number;
77
+ };
78
+ export interface AgentRunData {
79
+ title: string;
80
+ subtitle?: string;
81
+ status: AgentRunStatus;
82
+ /** Epoch ms this run (or turn) started: `Started 6:56 PM · 9m 22s`, live while running / waiting. */
83
+ startedAt?: number;
84
+ /** Epoch ms it ended: a finished run shows its run time instead of a clock. */
85
+ endedAt?: number;
86
+ /** Accumulated active time, shown as `Active …` when there is no start time. */
87
+ activeMs?: number;
88
+ plan?: {
89
+ steps: AgentRunStep[];
90
+ };
91
+ approvals?: AgentRunApproval[];
92
+ questions?: AgentRunQuestion[];
93
+ details?: Array<[label: string, value: AgentRunDetailValue]>;
94
+ /** `true` shows Stop while the run is live; a string shows it disabled with that reason. */
95
+ canStop?: boolean | string;
96
+ /** A stop request is in flight (Stop disabled, aria-busy). */
97
+ stopping?: boolean;
98
+ }
99
+ export interface AgentRunApproveDetail {
100
+ id: string;
101
+ decision: AgentRunDecision;
102
+ }
103
+ export interface AgentRunAnswerDetail {
104
+ questionId: string;
105
+ choiceId: string;
106
+ }
107
+ declare const STRINGS: {
108
+ en: {
109
+ chip: string;
110
+ status: Record<AgentRunStatus, string>;
111
+ statusSay: (w: string) => string;
112
+ started: (t: string) => string;
113
+ liveTitle: string;
114
+ ranTitle: string;
115
+ active: string;
116
+ activeTitle: string;
117
+ stop: string;
118
+ stopLabel: string;
119
+ plan: string;
120
+ planSteps: string;
121
+ noSteps: string;
122
+ stepSr: Record<AgentRunStepStatus, string>;
123
+ calls: (n: number) => string;
124
+ foldShow: (n: number) => string;
125
+ foldHide: string;
126
+ phase: Record<AgentRunPlanPhase["phase"], string>;
127
+ stepDone: (l: string) => string;
128
+ stepFailed: (l: string) => string;
129
+ needs: string;
130
+ apTitle: (tool: string) => string;
131
+ requested: (t: string) => string;
132
+ risk: string;
133
+ riskWord: string;
134
+ toolPill: string;
135
+ questionPill: string;
136
+ rules: (n: number) => "Matched dangerous-command rules" | "Matched dangerous-command rule";
137
+ truncated: string;
138
+ btn: {
139
+ explain: string;
140
+ approve_disable_rule: string;
141
+ deny: string;
142
+ approve: string;
143
+ };
144
+ expiresIn: (c: string) => string;
145
+ expired: string;
146
+ expiresAt: (t: string) => string;
147
+ answered: Record<AgentRunDecision, string>;
148
+ sent: Record<AgentRunDecision, string>;
149
+ arrived: (tool: string) => string;
150
+ recommended: string;
151
+ asked: (title: string) => string;
152
+ answeredQ: string;
153
+ answerSent: (label: string) => string;
154
+ details: string;
155
+ };
156
+ ko: {
157
+ chip: string;
158
+ status: Record<AgentRunStatus, string>;
159
+ statusSay: (w: string) => string;
160
+ started: (t: string) => string;
161
+ liveTitle: string;
162
+ ranTitle: string;
163
+ active: string;
164
+ activeTitle: string;
165
+ stop: string;
166
+ stopLabel: string;
167
+ plan: string;
168
+ planSteps: string;
169
+ noSteps: string;
170
+ stepSr: Record<AgentRunStepStatus, string>;
171
+ calls: (n: number) => string;
172
+ foldShow: (n: number) => string;
173
+ foldHide: string;
174
+ phase: Record<AgentRunPlanPhase["phase"], string>;
175
+ stepDone: (l: string) => string;
176
+ stepFailed: (l: string) => string;
177
+ needs: string;
178
+ apTitle: (tool: string) => string;
179
+ requested: (t: string) => string;
180
+ risk: string;
181
+ riskWord: string;
182
+ toolPill: string;
183
+ questionPill: string;
184
+ rules: (n: number) => "Matched dangerous-command rules" | "Matched dangerous-command rule";
185
+ truncated: string;
186
+ btn: {
187
+ explain: string;
188
+ approve_disable_rule: string;
189
+ deny: string;
190
+ approve: string;
191
+ };
192
+ expiresIn: (c: string) => string;
193
+ expired: string;
194
+ expiresAt: (t: string) => string;
195
+ answered: Record<AgentRunDecision, string>;
196
+ sent: Record<AgentRunDecision, string>;
197
+ arrived: (tool: string) => string;
198
+ recommended: string;
199
+ asked: (title: string) => string;
200
+ answeredQ: string;
201
+ answerSent: (label: string) => string;
202
+ details: string;
203
+ };
204
+ };
205
+ type Lang = keyof typeof STRINGS;
206
+ export interface AgentRunPlanPhase {
207
+ /** From the steps alone: any running → running; else any failed → failed; all done/skipped → done; else idle. */
208
+ phase: 'running' | 'done' | 'failed' | 'idle';
209
+ done: number;
210
+ total: number;
211
+ /** Earliest start (the running clock counts from here), 0 when unknown. */
212
+ since: number;
213
+ /** Last end − first start once the plan is finished, 0 when the steps do not carry both. */
214
+ tookMs: number;
215
+ }
216
+ /** The plan footer: phase, done/total and the time — since the first start while running, the total once finished. */
217
+ export declare function runPlanPhase(steps: AgentRunStep[]): AgentRunPlanPhase;
218
+ /** Indexes of the done steps folded away: every done step but the last `limit` (earliest first). */
219
+ export declare function runFoldedSteps(steps: AgentRunStep[], limit?: number): number[];
220
+ /**
221
+ * A tool input as approval-card rows. A JSON object becomes label → value rows (its string `description` becomes
222
+ * the summary); anything else — cut, not JSON, not an object — returns null so the host shows it as `raw`.
223
+ */
224
+ export declare function runApprovalFields(input: string, truncated?: boolean): {
225
+ summary: string;
226
+ fields: Array<[string, string]>;
227
+ } | null;
228
+ /** A field value as text: strings as they are, anything else as pretty JSON. */
229
+ export declare function runFieldText(v: unknown): string;
230
+ /** The recommended choice first, the rest in their order. */
231
+ export declare function runChoiceOrder(choices: AgentRunChoice[]): AgentRunChoice[];
232
+ /**
233
+ * A question card's identity: its whole meaning serialized (id, text and every choice's id, label, description,
234
+ * recommendation). A question rewritten in place is another card, so a focused choice is never rebound to it.
235
+ */
236
+ export declare function runQuestionKey(q: AgentRunQuestion): string;
237
+ /** Things still waiting on the person: unanswered approvals + unanswered questions. */
238
+ export declare function runNeedsCount(d: Pick<AgentRunData, 'approvals' | 'questions'>): number;
239
+ /** Time of day: `6:56 PM` (en) / `오후 06:56` (ko). */
240
+ export declare function runTimeOfDay(ts: number, lang?: Lang): string;
241
+ /**
242
+ * A DOM-safe, injective form of an identity string: [A-Za-z0-9] stay, every other UTF-16 unit becomes
243
+ * `_<base36 code>-`. Focus is restored by `data-key`, so a key must be exact (no hashing — two identities never
244
+ * share one) and plain enough for any selector engine.
245
+ */
246
+ export declare function runDomKey(identity: string): string;
247
+ /**
248
+ * `<ui-agent-run>` — one autonomous run as a screen: the head (status, started time with a live elapsed clock,
249
+ * Stop), the plan (a marker per step on one rail, the current step bold, per-step time and tool calls, a phase
250
+ * footer), "Needs input" (approval cards and question cards answered in place) and the details list.
251
+ */
252
+ export declare class UiAgentRun extends UiElement<AgentRunData> {
253
+ #private;
254
+ static styles: string[];
255
+ static get observedAttributes(): string[];
256
+ constructor();
257
+ attributeChangedCallback(): void;
258
+ disconnectedCallback(): void;
259
+ /**
260
+ * Keyboard landing (the dashboard's panel opening): focus the first unanswered card — the card, not a button, so
261
+ * Esc still reaches the page and ⌘/Ctrl+Enter is a deliberate second step. Returns false when nothing waits.
262
+ */
263
+ focusNeeds(): boolean;
264
+ protected update(): void;
265
+ protected updated(): void;
266
+ protected render(): string;
267
+ }
268
+ declare global {
269
+ interface HTMLElementTagNameMap {
270
+ 'ui-agent-run': UiAgentRun;
271
+ }
272
+ }
273
+ export {};
@@ -0,0 +1,2 @@
1
+ export type { AgentRunAnswerDetail, AgentRunApproval, AgentRunApproveDetail, AgentRunChoice, AgentRunData, AgentRunDecision, AgentRunDetailValue, AgentRunPlanPhase, AgentRunQuestion, AgentRunRule, AgentRunStatus, AgentRunStep, AgentRunStepStatus, } from './agent-run.js';
2
+ export { runApprovalFields, runChoiceOrder, runDomKey, runFieldText, runFoldedSteps, runNeedsCount, runPlanPhase, runQuestionKey, runTimeOfDay, UiAgentRun, } from './agent-run.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Ported from the soma-work dashboard session panel (.prd/affectui/re-agent-run.md): SESSION CHIP (D18), PLAN (D1–D7),
3
+ * 입력 필요 approval cards (D8–D17) and 세부 (D19). Every colour is a token or a color-mix of tokens, so light and dark
4
+ * follow the host tokens; the screen is one container (two columns from 760px), each approval card is its own.
5
+ */
6
+ export declare const styles = "\n:host { --ar-wait-text: color-mix(in srgb, var(--st-wait) 74%, var(--text)); }\n.ar { --ar-pad: 16px; container: ar / inline-size; position: relative; overflow: hidden; overflow: clip; border: 1px solid var(--border); border-radius: 16px; background: var(--surface); color: var(--text); font-size: 13px; line-height: 1.45; }\n\n/* \u2500\u2500 Head: title \u00B7 subtitle \u00B7 status pill \u00B7 started + live elapsed \u00B7 Stop \u2500\u2500 */\n.ar-head { padding: 14px var(--ar-pad) 12px; }\n.ar-title { margin: 0; font-size: 15px; font-weight: 650; line-height: 22px; letter-spacing: -0.005em; color: var(--text); overflow-wrap: anywhere; }\n.ar-title:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; border-radius: 4px; }\n.ar-sub { margin: 2px 0 0; font-size: 12px; line-height: 17px; color: var(--text-secondary); overflow-wrap: anywhere; }\n.ps-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; min-width: 0; margin-top: 10px; font-size: 12.5px; color: var(--text-secondary); }\n.ps-chip { --ag-c: var(--text-tertiary); --ag-t: var(--ag-c); display: inline-flex; align-items: center; gap: 5px; min-width: 0; height: 24px; padding: 0 10px 0 8px; border-radius: 99px; background: color-mix(in srgb, var(--ag-c) 13%, transparent); white-space: nowrap; }\n.ps-chip.is-running { --ag-c: var(--st-run); }\n.ps-chip.is-waiting { --ag-c: var(--st-wait); --ag-t: var(--ar-wait-text); }\n.ps-chip.is-done { --ag-c: var(--st-done); }\n.ps-chip.is-failed { --ag-c: var(--st-fail); --ag-t: var(--st-fail-text); }\n.ps-label { font-size: 11px; font-weight: 700; letter-spacing: 0.02em; color: var(--text-tertiary); }\n.ps-icon { display: inline-grid; place-items: center; width: 14px; height: 14px; color: var(--ag-c); }\n.ps-icon svg { width: 12px; height: 12px; }\n.ps-icon .ar-spin { width: 10px; height: 10px; }\n.ps-word { font-weight: 650; color: var(--ag-t); }\n.ps-time { display: inline-flex; align-items: center; gap: 5px; min-width: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }\n.ps-clock { display: inline-grid; flex: none; color: var(--text-tertiary); }\n.ps-clock svg { width: 13px; height: 13px; }\n.ps-dot { color: var(--text-tertiary); }\n.ps-actions { display: inline-flex; flex: none; align-items: center; gap: 6px; margin-left: auto; }\n.ar-stop { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 30px; padding: 3px 12px; border: 1px solid var(--ib-card-border); border-radius: 8px; background: var(--ib-card); color: var(--st-fail-text); font-size: 12.5px; font-weight: 500; white-space: nowrap; transition: border-color 150ms ease, transform var(--dur-press) var(--ease-out); }\n.ar-stop:disabled { cursor: not-allowed; opacity: 0.5; }\n.ar-stop:active:not(:disabled) { transform: scale(0.97); }\n\n/* \u2500\u2500 Bands: one column (plan \u2192 needs input \u2192 details); from 760px two \u2014 plan + details | side \u2500\u2500 */\n.ar-body { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; border-top: 1px solid var(--border); }\n.ar-band { min-width: 0; padding: 10px var(--ar-pad) 12px; }\n.ar-band + .ar-band { border-top: 1px solid var(--border); }\n.ar-sec-h { margin: 0 0 6px; font-size: 12px; font-weight: 700; line-height: 18px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-secondary); }\n@container ar (min-width: 760px) {\n .ar-head, .ar-body { --ar-pad: 20px; }\n .ar-head { padding-top: 16px; padding-bottom: 14px; }\n .ar-band { padding-top: 14px; padding-bottom: 14px; }\n .ar-body[data-side] { grid-template-columns: minmax(0, 1fr) minmax(340px, 440px); grid-template-rows: auto 1fr; }\n .ar-body[data-side] > .ar-band { border-top: 0; }\n .ar-body[data-side] > .ar-plan { grid-area: 1 / 1; }\n .ar-body[data-side=\"needs\"] > .ar-needs { grid-area: 1 / 2 / 3 / 3; align-self: stretch; border-left: 1px solid var(--border); }\n .ar-body[data-side=\"needs\"] > .ar-details { grid-area: 2 / 1; }\n .ar-body[data-side=\"needs\"] > .ar-plan ~ .ar-details { border-top: 1px solid var(--border); }\n .ar-body[data-side=\"details\"] > .ar-details { grid-area: 1 / 2 / 3 / 3; align-self: stretch; border-left: 1px solid var(--border); }\n}\n\n/* \u2500\u2500 PLAN: a marker per step (check \u00B7 spinner \u00B7 ring \u00B7 \u2715 \u00B7 dash) on one rail, time on the right, phase foot \u2500\u2500 */\n.pl-list { position: relative; margin: 0; padding: 0; list-style: none; }\n.pl-empty { margin: 0; padding: 2px 0 4px; font-size: 12px; color: var(--text-tertiary); }\n.pl-step { position: relative; display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; column-gap: 10px; align-items: start; padding: 5px 0; font-size: 12.5px; line-height: 18px; }\n.pl-marker { position: relative; z-index: 1; display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: var(--surface); box-shadow: inset 0 0 0 1px var(--ib-card-border); color: var(--text-secondary); }\n.pl-step[data-step=\"done\"] .pl-marker { background: var(--ib-pill); box-shadow: none; color: var(--text); }\n.pl-step[data-step=\"running\"] .pl-marker { box-shadow: none; color: var(--st-run); }\n.pl-step[data-step=\"failed\"] .pl-marker { background: color-mix(in srgb, var(--st-fail) 16%, var(--surface)); box-shadow: none; color: var(--st-fail-text); }\n.pl-step[data-step=\"skipped\"] .pl-marker { box-shadow: none; border: 1px dashed var(--text-tertiary); color: var(--text-tertiary); }\n.pl-marker svg { width: 10px; height: 10px; }\n.pl-marker .ar-spin { width: 11px; height: 11px; }\n.pl-step.pl-pop .pl-marker > * { animation: pl-pop var(--dur-move) var(--ease-out) both; }\n@keyframes pl-pop { from { opacity: 0; transform: scale(0.6); } }\n.pl-list > .pl-step:not(:last-child)::before { content: \"\"; position: absolute; left: 8.5px; top: 25px; bottom: -3px; width: 1px; background: var(--ib-card-border); }\n.pl-list > .pl-step[data-step=\"done\"]:not(:last-child)::after { content: \"\"; position: absolute; left: 8.5px; top: 25px; bottom: -3px; width: 1px; background: var(--text-tertiary); transform-origin: top; }\n.pl-list > .pl-step.pl-pop[data-step=\"done\"]:not(:last-child)::after { animation: pl-rail 560ms var(--ease-out) both; }\n@keyframes pl-rail { from { transform: scaleY(0); } }\n.pl-main { min-width: 0; }\n.pl-line { display: block; overflow-wrap: anywhere; }\n.pl-ref { margin-right: 6px; font-size: 11px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }\n.pl-text { color: var(--text-secondary); }\n.pl-step[data-step=\"running\"] .pl-text { font-weight: 600; color: var(--text); }\n.pl-step[data-step=\"failed\"] .pl-text { color: var(--text); }\n.pl-step[data-step=\"skipped\"] .pl-text { color: var(--text-tertiary); }\n.pl-chip { display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: 6px; background: var(--ib-pill); color: var(--text-secondary); font-size: 11px; font-weight: 400; line-height: 16px; white-space: nowrap; font-variant-numeric: tabular-nums; vertical-align: 1px; }\n.pl-sub { display: block; margin-top: 1px; overflow: hidden; font-size: 12px; color: var(--text-tertiary); text-overflow: ellipsis; white-space: nowrap; }\n.pl-step[data-step=\"failed\"] .pl-sub { color: var(--st-fail-text); white-space: normal; overflow-wrap: anywhere; }\n.pl-aside { min-height: 18px; font-size: 12px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }\n.pl-step[data-step=\"running\"] .pl-aside { color: var(--text); }\n.pl-fold { padding: 0 0 0 28px; }\n.pl-fold-btn { min-height: 28px; padding: 4px 0; border: 0; background: none; color: var(--accent); font-size: 11.5px; font-weight: 600; }\n.pl-fold-btn:hover { text-decoration: underline; }\n.pl-foot { position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; gap: 8px; margin: 6px calc(-1 * var(--ar-pad)) -12px; padding: 8px var(--ar-pad) 10px; border-top: 1px solid var(--border); background: var(--surface); font-size: 12.5px; color: var(--text-secondary); }\n.pl-foot-st { display: inline-flex; align-items: center; gap: 7px; min-width: 0; color: var(--text); font-variant-numeric: tabular-nums; }\n.pl-foot-icon { display: inline-grid; place-items: center; width: 14px; height: 14px; color: var(--text-tertiary); }\n.pl-foot[data-phase=\"running\"] .pl-foot-icon { color: var(--st-run); }\n.pl-foot[data-phase=\"done\"] .pl-foot-icon { color: var(--st-done); }\n.pl-foot[data-phase=\"failed\"] .pl-foot-icon { color: var(--st-fail-text); }\n.pl-foot-icon svg { width: 12px; height: 12px; }\n.pl-foot-icon .ar-spin { width: 11px; height: 11px; }\n.pl-foot-time { margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; }\n@container ar (min-width: 760px) { .pl-foot { margin-bottom: -14px; } }\n\n/* \u2500\u2500 \uC785\uB825 \uD544\uC694 / Needs input: a head with the count, then cards in a tray \u2500\u2500 */\n.ar-needs { padding-top: 4px; }\n.ar-needs-head { display: flex; align-items: center; gap: 8px; margin: 0; padding: 8px 0; font-size: 13px; font-weight: 650; line-height: 18px; color: var(--text); }\n.ar-needs-head:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; border-radius: 6px; }\n.pn-count { display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px; background: color-mix(in srgb, var(--st-wait) 24%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--st-wait) 55%, transparent); color: var(--text); font-size: 11px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }\n.pn-count.is-pop { animation: pn-pop var(--dur-move) var(--ease-out) both; }\n@keyframes pn-pop { from { opacity: 0; transform: scale(0.6); } }\n.ap-list { display: flex; flex-direction: column; gap: 10px; }\n.ap-card { container: ap / inline-size; position: relative; padding: 4px; border: 1px solid var(--ib-card-border); border-radius: 14px; background: var(--ib-col); color: var(--ib-text); }\n.ap-card:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }\n.ap-label { padding: 4px 8px 6px; overflow: hidden; font-size: 12px; line-height: 16px; color: var(--ib-muted); white-space: nowrap; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }\n.ap-body { padding: 14px; border-radius: 11px; background: var(--ib-card); box-shadow: 0 0 0 1px var(--ib-card-border); }\n.ap-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: 10px; }\n.ap-icon { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 8px; background: var(--ib-pill); color: var(--ib-text); }\n.ap-icon svg { width: 16px; height: 16px; }\n.ap-titles { min-width: 0; padding-top: 1px; }\n.ap-title { overflow: hidden; font-size: 14px; font-weight: 600; line-height: 20px; color: var(--ib-text); white-space: nowrap; text-overflow: ellipsis; }\n.ap-question .ap-title { overflow: visible; white-space: pre-line; overflow-wrap: anywhere; }\n.ap-desc { margin-top: 1px; overflow: hidden; font-size: 13px; line-height: 18px; color: var(--ib-text-2); white-space: nowrap; text-overflow: ellipsis; }\n.ap-qbody { margin-top: 4px; font-size: 13px; line-height: 18px; color: var(--ib-text-2); white-space: pre-line; overflow-wrap: anywhere; }\n.ap-pill { display: inline-flex; align-items: center; gap: 6px; height: 22px; margin-top: 1px; padding: 0 9px 0 8px; border-radius: 999px; background: var(--ib-pill); color: var(--ib-text-2); font-size: 12px; font-weight: 500; line-height: 1; white-space: nowrap; }\n.ap-pill::before { content: \"\"; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.8; }\n.ap-pill[data-kind=\"rule\"] { background: color-mix(in srgb, var(--st-fail) 14%, transparent); color: var(--st-fail-text); }\n.ap-pill[data-kind=\"question\"] { background: color-mix(in srgb, var(--st-wait) 16%, transparent); color: var(--ar-wait-text); }\n.ap-details { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; max-height: 320px; overflow: auto; margin: 12px 0 0; padding: 10px 12px; border-radius: 9px; background: var(--ib-col); box-shadow: inset 0 0 0 1px var(--ib-card-border); font-size: 12.5px; line-height: 18px; }\n.ap-detail { display: contents; }\n.ap-detail dt { color: var(--ib-muted); }\n.ap-detail dd { min-width: 0; margin: 0; color: var(--ib-text); font-family: var(--mono); font-size: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }\n.ap-raw { max-height: 320px; overflow: auto; margin: 12px 0 0; padding: 10px 12px; border-radius: 9px; background: var(--ib-col); box-shadow: inset 0 0 0 1px var(--ib-card-border); color: var(--ib-text); font-family: var(--mono); font-size: 12px; line-height: 18px; white-space: pre-wrap; overflow-wrap: anywhere; }\n.ap-truncated { margin: 4px 0 0; font-size: 11.5px; color: var(--ib-muted); }\n.ap-rules { display: flex; flex-direction: column; gap: 3px; margin: 10px 0 0; font-size: 12px; line-height: 17px; color: var(--ib-text); overflow-wrap: anywhere; }\n.ap-rules-label { font-weight: 600; color: var(--ib-text-2); }\n.ap-rule code { padding: 1px 5px; border-radius: 4px; background: color-mix(in srgb, var(--orange) 14%, transparent); color: var(--orange); font-family: var(--mono); font-size: 0.95em; }\n.ap-alts { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 10px; }\n.ap-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-top: 8px; }\n.ap-expires { font-size: 12.5px; color: var(--ib-muted); font-variant-numeric: tabular-nums; white-space: nowrap; transition: color var(--dur-fade) linear; }\n.ap-expires.is-low { color: var(--ar-wait-text); }\n.ap-actions { display: flex; flex: 0 0 auto; gap: 4px; margin-left: auto; }\n.ap-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 34px; padding: 0 12px; border: 0; border-radius: 10px; font-size: 13px; font-weight: 500; line-height: 1; white-space: nowrap; transition: background-color 150ms ease, opacity 150ms ease, transform var(--dur-press) var(--ease-out); }\n.ap-ghost { min-height: 28px; padding: 0 10px; border-radius: 8px; background: transparent; color: var(--ib-text-2); font-size: 12.5px; box-shadow: inset 0 0 0 1px var(--ib-card-border); }\n.ap-quiet { background: transparent; color: var(--ib-text); box-shadow: inset 0 0 0 1px var(--ib-card-border); }\n.ap-primary { background: var(--ib-text); color: var(--ib-card); }\n.ap-btn:disabled, .ap-choice:disabled { cursor: not-allowed; opacity: 0.5; }\n.ap-btn.is-busy, .ap-choice.is-busy { opacity: 1; cursor: progress; }\n.ap-btn:active:not(:disabled), .ap-choice:active:not(:disabled) { transform: scale(0.97); }\n.ap-kbd { display: inline-grid; place-items: center; min-width: 20px; height: 20px; margin-right: -4px; padding: 0 5px; border-radius: 6px; background: color-mix(in srgb, currentColor 12%, transparent); font-size: 11px; font-weight: 500; opacity: 0.8; }\n.ap-btn:disabled .ap-kbd, .ap-choice:disabled .ap-kbd { display: none; }\n.ap-choices { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }\n.ap-choice { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; min-height: 36px; padding: 8px 12px; border: 0; border-radius: 10px; background: transparent; color: var(--ib-text); box-shadow: inset 0 0 0 1px var(--ib-card-border); font-size: 13px; line-height: 18px; text-align: left; transition: background-color 150ms ease, opacity 150ms ease, transform var(--dur-press) var(--ease-out); }\n.ap-rec { padding-right: 52px; background: var(--ib-text); color: var(--ib-card); box-shadow: none; font-weight: 500; }\n.ap-rec .ap-kbd { position: absolute; top: 8px; right: 8px; margin: 0; background: color-mix(in srgb, currentColor 14%, transparent); }\n.ap-choice-label { overflow-wrap: anywhere; }\n.ap-choice-label .ar-spin { margin-right: 6px; }\n.ap-choice-tag { margin-left: 8px; padding: 1px 6px; border-radius: 6px; background: color-mix(in srgb, currentColor 14%, transparent); font-size: 11px; font-weight: 500; white-space: nowrap; }\n.ap-choice-desc { font-size: 12px; font-weight: 400; opacity: 0.75; overflow-wrap: anywhere; }\n.ap-lock { margin: 8px 0 0; font-size: 12px; color: var(--ib-muted); overflow-wrap: anywhere; }\n.ap-note { margin: 8px 0 0; font-size: 12px; color: var(--st-fail-text); overflow-wrap: anywhere; }\n.ap-resolved { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 10px 12px; border-radius: 11px; background: var(--ib-card); box-shadow: 0 0 0 1px var(--ib-card-border); }\n.ap-resolved.is-enter { animation: ap-in var(--dur-move) var(--ease-out) both; }\n@keyframes ap-in { from { opacity: 0; transform: translateY(4px); } }\n.ap-mark { display: grid; flex: none; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: color-mix(in srgb, var(--st-done) 16%, transparent); color: var(--st-done); font-size: 12px; font-weight: 700; }\n.ap-mark svg { width: 12px; height: 12px; }\n.ap-resolved[data-decision=\"deny\"] .ap-mark, .ap-resolved[data-decision=\"explain\"] .ap-mark { background: var(--ib-pill); color: var(--ib-text-2); }\n.ap-row-title { flex: 1 1 auto; min-width: 0; overflow: hidden; font-size: 13.5px; font-weight: 500; color: var(--ib-text); white-space: nowrap; text-overflow: ellipsis; }\n.ap-row-status { flex: 0 1 auto; min-width: 0; overflow: hidden; font-size: 12.5px; color: var(--ib-muted); white-space: nowrap; text-overflow: ellipsis; }\n/* An answered question keeps its whole answer readable: the answer goes on a second line under the question. */\n.ap-resolved[data-decision=\"answer\"] { display: grid; grid-template-columns: 22px minmax(0, 1fr); column-gap: 10px; row-gap: 1px; }\n.ap-resolved[data-decision=\"answer\"] .ap-row-status { grid-column: 2; white-space: normal; overflow-wrap: anywhere; }\n@container ap (max-width: 379px) {\n .ap-actions { flex: 1 1 100%; margin-left: 0; }\n .ap-actions .ap-btn { flex: 1 1 0; min-width: 0; }\n .ap-kbd { display: none; }\n .ap-rec { padding-right: 12px; }\n}\n@container ap (max-width: 319px) {\n .ap-body { padding: 12px; }\n .ap-pill-word { display: none; }\n}\n\n/* \u2500\u2500 \uC138\uBD80 / Details: label left, value right \u2500\u2500 */\n.pi-details { display: grid; gap: 7px; margin: 0; }\n.pi-details > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; }\n.pi-details dt { flex: 0 1 auto; min-width: 0; font-size: 12px; color: var(--text-tertiary); }\n.pi-details dd { min-width: 0; margin: 0; font-size: 12.5px; font-weight: 600; color: var(--text); text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }\n\n/* \u2500\u2500 Spinner \u2500\u2500 */\n.ar-spin { display: inline-block; flex: none; width: 12px; height: 12px; border: 2px solid var(--ib-pill); border-top-color: var(--st-run); border-right-color: var(--st-run); border-radius: 50%; vertical-align: -1px; animation: ar-spin 0.9s linear infinite; }\n.ap-btn .ar-spin, .ap-choice .ar-spin, .ar-stop .ar-spin { width: 11px; height: 11px; border-width: 1.5px; border-color: color-mix(in srgb, currentColor 25%, transparent); border-top-color: currentColor; border-right-color: currentColor; }\n@keyframes ar-spin { to { transform: rotate(360deg); } }\n\n/* Key hints only with a hover pointer; quiet hovers; 44px answers on touch. */\n@media (hover: none) {\n .ap-kbd { display: none; }\n .ap-rec { padding-right: 12px; }\n}\n@media (hover: hover) and (pointer: fine) {\n .ap-ghost:not(:disabled):hover, .ap-quiet:not(:disabled):hover, .ap-choice:not(.ap-rec):not(:disabled):hover { background: var(--ib-pill); }\n .ap-primary:not(:disabled):hover, .ap-rec:not(:disabled):hover { opacity: 0.88; }\n .ar-stop:not(:disabled):hover { border-color: var(--st-fail); }\n}\n@media (pointer: coarse) {\n .ap-btn, .ap-choice, .ar-stop, .pl-fold-btn { min-height: 44px; }\n}\n";
@@ -0,0 +1,92 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ /**
3
+ * One assistant reply. Links are read out of the markdown itself — they are links the answer contains, not
4
+ * proven sources, so the UI calls them “links” (never “sources” / “citations”).
5
+ */
6
+ export interface AiAnswerData {
7
+ /** The answer as the model wrote it (markdown). Rendered safely: raw HTML never survives. */
8
+ markdown: string;
9
+ /** Time from the instruction to the answer, in ms. Shown in the footer; nothing when absent or ≤ 0. */
10
+ responseMs?: number;
11
+ /** More text is coming: shows the writing row, hides Copy, marks the card `aria-busy`. */
12
+ streaming?: boolean;
13
+ /** The host is fetching a recorded answer (no text yet). */
14
+ loading?: boolean;
15
+ /** Optional heading above the card (e.g. the question or the turn’s summary title). */
16
+ title?: string;
17
+ /** Failure detail (e.g. `HTTP 404`). Without text it replaces the answer; with text it shows in the footer. */
18
+ error?: string;
19
+ /** Show “Try again” next to an error (emits `ui-retry`). Default false. */
20
+ canRetry?: boolean;
21
+ /** Titles the host already knows for some URLs (e.g. the PR this session opened). Beat labels and URL-derived titles. */
22
+ linkTitles?: Record<string, string>;
23
+ }
24
+ /** A link found in the answer, numbered by first appearance (one per URL; a trailing slash is the same URL). */
25
+ export interface AnswerLink {
26
+ /** 1-based number shown on the card. */
27
+ n: number;
28
+ href: string;
29
+ /** Site: `GitHub` or the host without `www.`. */
30
+ name: string;
31
+ title: string;
32
+ /** Short chip label: `#123`, a 7-char sha, `GitHub` or the host. */
33
+ chip: string;
34
+ }
35
+ export type AnswerLang = 'en' | 'ko';
36
+ export interface AnswerRender {
37
+ html: string;
38
+ links: AnswerLink[];
39
+ }
40
+ /** One link per URL: a trailing slash does not make another page. */
41
+ export declare function answerLinkKey(href: string): string;
42
+ /**
43
+ * The dashboard’s `ansLinkify`: every link in the answer as a markdown link. Markdown links are protected first
44
+ * (a label may be a code span, a target may hold balanced parens — encoded), code spans and fenced blocks (also
45
+ * inside a `>` quote; a quoted fence ends with the quote) stay as written, only http(s) is linked.
46
+ */
47
+ export declare function answerLinkify(raw: string): string;
48
+ /** Builds the markup for one http(s) link: `href` is plain text, `labelHtml` is already safe HTML. */
49
+ export type LinkRenderer = (href: string, labelHtml: string) => string;
50
+ /**
51
+ * Markdown as a document — the dashboard’s `renderMarkdownDoc`: headings, paragraphs, nested lists, tables,
52
+ * fenced code, quotes, rules. Everything is escaped before it is marked up; only http(s) links are links.
53
+ */
54
+ export declare function answerMarkdown(src: string, link?: LinkRenderer): string;
55
+ /** What a link card is called — name (site), title and chip label (the dashboard’s `ansSourceOf`). */
56
+ export declare function answerLinkInfo(href: string, label?: string, lang?: AnswerLang, titles?: Record<string, string>): Omit<AnswerLink, 'n'>;
57
+ /**
58
+ * The answer as a document plus its links (the dashboard’s `renderAnswer`). Links are numbered by first appearance;
59
+ * every link becomes a chip — a bare URL only the chip, a labelled link its words then the chip. A `[1]` the answer
60
+ * writes without a URL stays text (no invented citations).
61
+ */
62
+ export declare function renderAnswer(markdown: string, opts?: {
63
+ lang?: AnswerLang;
64
+ titles?: Record<string, string>;
65
+ }): AnswerRender;
66
+ /** What Copy puts on the clipboard: the answer as written (markdown kept) plus its numbered links. */
67
+ export declare function answerCopyText(markdown: string, links: AnswerLink[], lang?: AnswerLang): string;
68
+ /** “Responded in 4m 12s” / “응답 4분 12초”; empty for ≤ 0 or not a number. */
69
+ export declare function answerTookLabel(ms: number, lang?: AnswerLang): string;
70
+ /**
71
+ * `<ui-ai-answer>` — an assistant reply as a card: the markdown rendered safely, the links it contains as a
72
+ * numbered row of link cards plus chips in the text (hover / focus lights the pair), Copy (`ui-copy`), the response
73
+ * time, a writing state while it streams, and loading / empty / failed states.
74
+ */
75
+ export declare class UiAiAnswer extends UiElement<AiAnswerData> {
76
+ #private;
77
+ static styles: string[];
78
+ static get observedAttributes(): string[];
79
+ constructor();
80
+ attributeChangedCallback(): void;
81
+ disconnectedCallback(): void;
82
+ /** The links of the last render, in card order. */
83
+ get links(): AnswerLink[];
84
+ protected update(): void;
85
+ protected render(): string;
86
+ protected updated(): void;
87
+ }
88
+ declare global {
89
+ interface HTMLElementTagNameMap {
90
+ 'ui-ai-answer': UiAiAnswer;
91
+ }
92
+ }
@@ -0,0 +1,2 @@
1
+ export type { AiAnswerData, AnswerLang, AnswerLink, AnswerRender, LinkRenderer } from './ai-answer.js';
2
+ export { answerCopyText, answerLinkInfo, answerLinkify, answerLinkKey, answerMarkdown, answerTookLabel, renderAnswer, UiAiAnswer, } from './ai-answer.js';
@@ -0,0 +1,2 @@
1
+ /** Ported from the soma-work dashboard (.prd/affectui/re-ai-answer.md D2 · D6–D10 · D15–D18, `.ans-*` + `.md-doc`). */
2
+ export declare const styles = "\n.ui-root { container: ans / inline-size; }\n.ans-card { position: relative; padding: 12px 14px 10px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--text); box-shadow: var(--ib-shadow); overflow: hidden; }\n.ans-card.is-in > * { animation: ans-in var(--dur-fade) var(--ease-out) both; }\n.ans-title { margin: 0 0 10px; font-size: 15px; font-weight: 600; line-height: 1.35; letter-spacing: -0.01em; color: var(--text); overflow-wrap: anywhere; }\n\n/* Link cards (D6): a sideways row, scrollbar hidden, both ends fade. */\n.ans-srcs { display: flex; gap: 8px; margin: -4px -14px 8px; padding: 4px 14px; list-style: none; overflow-x: auto; scrollbar-width: none;\n -webkit-mask: linear-gradient(90deg, transparent, black 12px, black calc(100% - 12px), transparent);\n mask: linear-gradient(90deg, transparent, black 12px, black calc(100% - 12px), transparent); }\n.ans-srcs::-webkit-scrollbar { display: none; }\n.ans-srcs > li { flex: none; display: flex; }\n.ans-src { display: flex; flex-direction: column; gap: 4px; width: 148px; min-height: 64px; padding: 8px 10px; border-radius: 10px; background: var(--surface-raised); color: inherit; text-decoration: none; transition: background-color var(--dur-press) ease, box-shadow var(--dur-press) ease; }\n.ans-card.is-in .ans-src { animation: ans-pop var(--dur-move) var(--ease-out) both; animation-delay: calc(var(--ans-i, 0) * 60ms); }\n.ans-src:hover { background: var(--ib-pill); }\n.ans-src.is-lit { box-shadow: inset 0 0 0 1.5px var(--text-tertiary); }\n.ans-src-site { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 11px; color: var(--text-secondary); }\n.ans-src-name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.ans-src-num { flex: none; font-variant-numeric: tabular-nums; color: var(--text-tertiary); }\n.ans-src-title { display: -webkit-box; overflow: hidden; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-size: 12px; line-height: 1.4; color: var(--text); word-break: break-word; }\n\n/* Label (D9) and body (D2). */\n.ans-label { margin: 0 0 6px; font-size: 12px; font-weight: 600; color: var(--text-secondary); }\n.ans-body { font-size: 13.5px; line-height: 1.6; }\n.ans-body > :last-child { margin-bottom: 0; }\n\n/* The document (dashboard .md-doc). */\n.md-doc { color: var(--text); word-break: break-word; }\n.md-doc h1, .md-doc h2, .md-doc h3, .md-doc h4, .md-doc h5, .md-doc h6 { margin: 1.3em 0 0.5em; line-height: 1.3; letter-spacing: -0.01em; }\n.md-doc h1, .md-doc h2 { font-size: 1.15em; }\n.md-doc h3, .md-doc h4, .md-doc h5, .md-doc h6 { font-size: 1em; }\n.md-doc > :first-child { margin-top: 0; }\n.md-doc p, .md-doc ul, .md-doc ol, .md-doc blockquote, .md-doc pre, .md-doc .md-table { margin: 0 0 0.9em; }\n.md-doc ul, .md-doc ol { padding-left: 1.4em; }\n.md-doc li + li { margin-top: 0.2em; }\n.md-doc li > ul, .md-doc li > ol { margin: 0.2em 0 0; }\n.md-doc code { padding: 0.1em 0.35em; border-radius: 4px; background: var(--ib-pill); font: 0.88em var(--mono); }\n.md-doc pre { padding: 12px 14px; overflow: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--ib-col); }\n.md-doc pre code { padding: 0; background: none; font-size: 12.5px; line-height: 1.55; }\n.md-doc blockquote { padding: 2px 0 2px 12px; border-left: 3px solid var(--border-focus); color: var(--text-secondary); }\n.md-doc blockquote > :last-child { margin-bottom: 0; }\n.md-doc hr { margin: 1.4em 0; border: 0; border-top: 1px solid var(--border); }\n.md-doc a { color: var(--accent); }\n.md-doc .md-table { overflow-x: auto; }\n.md-doc table { border-collapse: collapse; font-size: 0.93em; }\n.md-doc th, .md-doc td { padding: 6px 10px; border: 1px solid var(--border); text-align: left; vertical-align: top; }\n.md-doc th { background: var(--ib-col); font-weight: 600; }\n\n/* Link chips (D7) \u2014 lit = inverted (D8). */\n.ans-body .ans-cite { display: inline-block; max-width: 16ch; height: 18px; margin: 0 1px 0 3px; padding: 0 6px; overflow: hidden; border-radius: 6px; background: var(--surface-raised); font-size: 11px; font-weight: 500; line-height: 18px; color: var(--text-secondary); text-decoration: none; white-space: nowrap; text-overflow: ellipsis; vertical-align: middle; transition: background-color var(--dur-press) ease, color var(--dur-press) ease; }\n.ans-body .ans-cite:hover { background: var(--ib-pill); color: var(--text); }\n.ans-body .ans-cite.is-lit { background: var(--text); color: var(--surface); }\n.ans-nb { white-space: nowrap; }\n.ans-cite:focus-visible, .ans-src:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }\n\n/* Footer (D10): Copy \u00B7 status \u00B7 response time. */\n.ans-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }\n.ans-btn { display: inline-flex; flex: none; align-items: center; justify-content: center; gap: 5px; min-height: 32px; padding: 5px 12px; border: 1px solid var(--ib-card-border); border-radius: 8px; background: var(--ib-card); font-size: 13px; font-weight: 500; color: var(--ib-text); white-space: nowrap; transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease; }\n.ans-btn svg { width: 13px; height: 13px; }\n.ans-copy[data-copied] { color: var(--green); }\n.ans-status { flex: 1 1 0; min-width: 0; font-size: 12px; color: var(--text-tertiary); }\n.ans-end { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px 10px; margin-left: auto; min-width: 0; }\n.ans-took { font-size: 11.5px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }\n@media (hover: hover) and (pointer: fine) { .ans-btn:hover { border-color: var(--ib-muted); } }\n\n/* Writing (D12): a sweep over the words + a pulsing run-colour dot. */\n.ans-foot.is-writing { min-height: 41px; }\n.ans-writing { display: flex; align-items: center; gap: 8px; font-size: 12px; }\n.ans-shimmer { color: transparent; background: linear-gradient(90deg, var(--text-tertiary) 38%, var(--text) 50%, var(--text-tertiary) 62%) 100% 0 / 300% 100% no-repeat; -webkit-background-clip: text; background-clip: text; animation: ans-sweep 1.6s linear infinite; }\n.ans-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--st-run); animation: ans-pulse 1s ease-in-out infinite; }\n\n/* Loading \u00B7 empty \u00B7 failed (D13). */\n.ans-state { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin: 0; font-size: 12px; color: var(--text-tertiary); }\n.ans-error { font-size: 12px; color: var(--st-fail-text); overflow-wrap: anywhere; }\n\n@keyframes ans-in { from { opacity: 0; filter: blur(2px); transform: translateY(3px); } to { opacity: 1; filter: none; transform: none; } }\n@keyframes ans-pop { from { opacity: 0; transform: translateY(6px) scale(0.96); } to { opacity: 1; transform: none; } }\n@keyframes ans-sweep { from { background-position: 100% 0; } to { background-position: 0 0; } }\n@keyframes ans-pulse { 50% { transform: scale(0.6); opacity: 0.45; } }\n\n@container ans (min-width: 520px) {\n .ans-card { padding: 16px 18px 12px; }\n .ans-title { font-size: 16.5px; }\n .ans-srcs { margin: -4px -18px 10px; padding: 4px 18px; }\n .ans-src { width: 172px; }\n .ans-body { font-size: 14px; }\n}\n\n/* Touch (D16): the chip's hit area grows, chips are never cut, buttons are 44px. */\n@media (pointer: coarse) {\n .ans-body .ans-cite { position: relative; overflow: visible; max-width: none; }\n .ans-cite::after { content: ''; position: absolute; inset: -10px -4px; }\n .ans-btn { min-height: 44px; }\n}\n/* The global rule only shortens durations, so infinite loops would flicker: stop them. */\n@media (prefers-reduced-motion: reduce) {\n .ans-shimmer, .ans-dot, .ans-src, .ans-card.is-in > * { animation: none !important; }\n .ans-shimmer { background: none; color: var(--text-tertiary); }\n .ans-src, .ans-body .ans-cite { transition: none; }\n}\n";
@@ -0,0 +1,156 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ /** What fills a model's context window, in prompt order. Tokens of the last request. */
3
+ export interface ContextParts {
4
+ /** Cached prefix read back (cache read). */
5
+ cacheRead?: number;
6
+ /** Newly cached this request (cache write). */
7
+ cacheWrite?: number;
8
+ /** Uncached input tail. */
9
+ input?: number;
10
+ /** The last response. */
11
+ output?: number;
12
+ }
13
+ /** Host-owned state of a compaction request. The component adds the two-press confirm itself. */
14
+ export type CompactState = 'idle' | 'sending' | 'sent' | 'queued' | 'observed' | 'failed';
15
+ export interface ContextWindowData {
16
+ /** Model id shown as a chip (e.g. `opus-4-5[1m]`). */
17
+ model?: string;
18
+ /** Window size in tokens. Nothing renders without it. */
19
+ window: number;
20
+ parts?: ContextParts;
21
+ /** The usage is one total that cannot be split into cache categories. */
22
+ unsplit?: {
23
+ tokens: number;
24
+ kind: 'compact' | 'unknown';
25
+ };
26
+ /** Auto-compaction threshold in tokens (drawn on the field, drives the warning level). */
27
+ autoCompactAt?: number;
28
+ /** The next input will auto-compact. */
29
+ compactPending?: boolean;
30
+ /** Last compaction's before/after, shown once a compaction is observed. */
31
+ lastCompact?: {
32
+ preTokens: number;
33
+ postTokens: number;
34
+ };
35
+ /** `true` shows the compact action; a string shows it disabled with that reason. Default: hidden. */
36
+ canCompact?: boolean | string;
37
+ compactState?: CompactState;
38
+ }
39
+ export type ContextLevel = 'ok' | 'warn' | 'full';
40
+ export declare const CELLS = 216;
41
+ declare const STRINGS: {
42
+ en: {
43
+ title: string;
44
+ of: (w: string) => string;
45
+ share: (label: string, pct: string) => string;
46
+ parts: {
47
+ read: string;
48
+ write: string;
49
+ input: string;
50
+ output: string;
51
+ };
52
+ unsplit: {
53
+ compact: string;
54
+ unknown: string;
55
+ };
56
+ mark: string;
57
+ sr: (u: string, w: string, p: string, a: string) => string;
58
+ left: (n: string) => string;
59
+ soon: (n: string) => string;
60
+ over: string;
61
+ free: (n: string) => string;
62
+ pending: string;
63
+ sending: string;
64
+ sent: string;
65
+ queued: string;
66
+ observed: string;
67
+ saved: (n: string, a: string, b: string) => string;
68
+ failed: string;
69
+ armed: string;
70
+ armedSr: string;
71
+ act: {
72
+ idle: string;
73
+ armed: string;
74
+ sending: string;
75
+ sent: string;
76
+ done: string;
77
+ };
78
+ actLabel: (l: string) => string;
79
+ };
80
+ ko: {
81
+ title: string;
82
+ of: (w: string) => string;
83
+ share: (label: string, pct: string) => string;
84
+ parts: {
85
+ read: string;
86
+ write: string;
87
+ input: string;
88
+ output: string;
89
+ };
90
+ unsplit: {
91
+ compact: string;
92
+ unknown: string;
93
+ };
94
+ mark: string;
95
+ sr: (u: string, w: string, p: string, a: string) => string;
96
+ left: (n: string) => string;
97
+ soon: (n: string) => string;
98
+ over: string;
99
+ free: (n: string) => string;
100
+ pending: string;
101
+ sending: string;
102
+ sent: string;
103
+ queued: string;
104
+ observed: string;
105
+ saved: (n: string, a: string, b: string) => string;
106
+ failed: string;
107
+ armed: string;
108
+ armedSr: string;
109
+ act: {
110
+ idle: string;
111
+ armed: string;
112
+ sending: string;
113
+ sent: string;
114
+ done: string;
115
+ };
116
+ actLabel: (l: string) => string;
117
+ };
118
+ };
119
+ type Strings = (typeof STRINGS)['en'];
120
+ interface Part {
121
+ key: string;
122
+ label: string;
123
+ tokens: number;
124
+ color: string;
125
+ }
126
+ /** Parts in prompt order; zero parts are left out; an unsplit total is one row. */
127
+ export declare function contextParts(d: ContextWindowData, t?: Strings): Part[];
128
+ /** Cell → part index (-1 free): cumulative rounding in part order, every non-empty part ≥ 1 cell. */
129
+ export declare function cellMap(parts: Array<{
130
+ tokens: number;
131
+ }>, limit: number, cells?: number): number[];
132
+ /** First cell past the auto-compact point, -1 when none inside the window. */
133
+ export declare function markIndex(auto: number, limit: number, cells?: number): number;
134
+ /** Warning level against the auto-compact point (≥80% warn, ≥95% full), else against the window (>60 / >80%). */
135
+ export declare function contextLevel(used: number, window: number, auto?: number, pending?: boolean): ContextLevel;
136
+ /**
137
+ * `<ui-context-window>` — what fills the model's context: 216 cells (one per window/216 tokens) coloured by
138
+ * cache category in prompt order, the auto-compact point on the field, a level against that point, and an
139
+ * optional two-press "Compact now" action (`ui-compact`).
140
+ */
141
+ export declare class UiContextWindow extends UiElement<ContextWindowData> {
142
+ #private;
143
+ static styles: string[];
144
+ static get observedAttributes(): string[];
145
+ attributeChangedCallback(): void;
146
+ constructor();
147
+ disconnectedCallback(): void;
148
+ protected render(): string;
149
+ protected updated(): void;
150
+ }
151
+ declare global {
152
+ interface HTMLElementTagNameMap {
153
+ 'ui-context-window': UiContextWindow;
154
+ }
155
+ }
156
+ export {};
@@ -0,0 +1,2 @@
1
+ export type { CompactState, ContextLevel, ContextParts, ContextWindowData } from './context-window.js';
2
+ export { CELLS, cellMap, contextLevel, contextParts, markIndex, UiContextWindow } from './context-window.js';
@@ -0,0 +1,2 @@
1
+ /** Ported from the soma-work dashboard (.prd/affectui/re-context-window.md D5–D19). */
2
+ export declare const styles = "\n:host { --cw-c1: var(--accent); --cw-c2: color-mix(in srgb, var(--accent) 42%, var(--ib-pill)); --cw-c3: var(--purple); --cw-c4: var(--green);\n --cw-free: var(--ib-pill); --cw-warn: var(--st-wait); --cw-warn-bg: color-mix(in srgb, var(--st-wait) 14%, transparent);\n --cw-full-bg: color-mix(in srgb, var(--st-fail) 12%, transparent); --cw-ok: var(--green); }\n.cw { container: cw / inline-size; max-width: 640px; position: relative; padding-bottom: 2px; font-size: 12.5px; color: var(--text-secondary); }\n.cw-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 22px; }\n.cw-title { font-size: 13px; font-weight: 600; color: var(--text); }\n.cw-model { flex: none; max-width: 62%; height: 20px; padding: 0 7px; border-radius: 5px; background: var(--ib-pill); font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size: 11px; line-height: 20px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cw-figure { display: flex; align-items: baseline; gap: 7px; margin-top: 6px; }\n.cw-big { font-size: 24px; font-weight: 600; line-height: 30px; letter-spacing: -0.02em; color: var(--text); font-variant-numeric: tabular-nums; }\n.cw-of { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--text-tertiary); }\n.cw-pill { flex: none; padding: 0 6px; border-radius: 5px; line-height: 20px; font-variant-numeric: tabular-nums; color: var(--text-secondary); transition: color 300ms ease, background-color 300ms ease; }\n.cw[data-level=\"warn\"] .cw-pill { color: var(--cw-warn); background: var(--cw-warn-bg); }\n.cw[data-level=\"full\"] .cw-pill { color: var(--st-fail-text); background: var(--cw-full-bg); font-weight: 600; }\n.cw-field { display: grid; grid-template-columns: repeat(36, minmax(0, 1fr)); gap: 2px; margin-top: 10px; }\n.cw-cell { display: block; aspect-ratio: 1; border-radius: 2px; background: var(--c, var(--cw-free)); transition: background-color 380ms var(--ease-out), opacity 220ms ease, transform var(--dur-move) var(--ease-out), box-shadow 380ms var(--ease-out); transition-delay: calc(var(--i) * 2ms), 0s, 0s, 0s; }\n/* Past the auto-compact point an empty cell is a hollow square: the session compacts before it fills. */\n.cw-cell[data-past]:not([data-part]) { background: transparent; box-shadow: inset 0 0 0 1px var(--border); }\n.cw-field[data-hot] .cw-cell[data-part]:not([data-on]) { opacity: 0.22; }\n.cw-cell[data-on] { transform: scale(1.12); }\n.cw-cell[data-busy] { opacity: 0.5; }\n@keyframes cw-wave { 0%, 100% { opacity: 1; transform: none; } 50% { opacity: 0.28; transform: scale(0.72); } }\n@keyframes cw-swap-in { from { opacity: 0; filter: blur(2px); } }\n@keyframes cw-leave { to { opacity: 0; filter: blur(3px); transform: scale(0.92); } }\n@keyframes cw-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: no-preference) {\n .cw-cell[data-busy] { animation: cw-wave 900ms ease-in-out infinite; animation-delay: calc(var(--i) * 15ms); }\n .cw-spin { animation: cw-spin 700ms linear infinite; }\n}\n.cw-parts { list-style: none; margin: 8px 0 0; padding: 0; }\n.cw-part, .cw-mark { display: flex; align-items: center; gap: 9px; min-height: 30px; margin: 0 -8px; padding: 0 8px; border-radius: 8px; transition: background-color var(--dur-press) ease, opacity 220ms ease; }\n.cw-part[data-hot] { background: var(--ib-pill); }\n.cw-part[data-dim], .cw-mark[data-dim] { opacity: 0.55; }\n.cw-sw { flex: none; width: 7px; height: 7px; border-radius: 2px; }\n.cw-sw.is-mark { background: transparent; box-shadow: inset 0 0 0 1px var(--text-tertiary); }\n.cw-label { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 13px; color: var(--text); }\n.cw-mark .cw-label { color: var(--text-secondary); }\n.cw-val { flex: none; min-width: 52px; text-align: right; font-size: 13px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }\n.cw-foot { display: flex; align-items: center; gap: 8px; min-height: 38px; margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border); }\n.cw-say { flex: 1; min-width: 0; display: grid; }\n.cw-say-t { grid-area: 1 / 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--text-secondary); font-variant-numeric: tabular-nums; animation: cw-swap-in 320ms ease both; }\n.cw-say-t b { font-weight: 600; color: var(--text); }\n.cw-say-t.is-warn { color: var(--cw-warn); }\n.cw-say-t.is-full, .cw-say-t.is-fail { color: var(--st-fail-text); }\n.cw-say-t.is-ok, .cw-say-t.is-ok b { color: var(--cw-ok); }\n.cw-compact { display: inline-flex; flex: none; align-items: center; gap: 5px; min-height: 28px; padding: 0 9px 0 7px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text); white-space: nowrap; cursor: pointer; transition: background-color var(--dur-press) ease, color var(--dur-press) ease, border-color var(--dur-press) ease; }\n.cw-compact:hover:not(:disabled) { background: var(--surface-raised); }\n.cw-compact:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.cw-compact:disabled { cursor: default; opacity: 0.5; }\n.cw-compact[data-state=\"armed\"] { border-color: var(--cw-warn); color: var(--cw-warn); }\n.cw-compact[data-state=\"busy\"], .cw-compact[data-state=\"done\"] { opacity: 1; }\n.cw-compact[data-state=\"done\"] { border-color: transparent; background: transparent; color: var(--cw-ok); }\n.cw-compact[data-leaving] { animation: cw-leave 400ms var(--ease-out) both; }\n.cw-ci, .cw-ct { display: inline-flex; align-items: center; animation: cw-swap-in 300ms ease both; }\n.cw-ci svg { width: 12px; height: 12px; }\n.cw-spin { display: block; width: 12px; height: 12px; border: 1.5px solid var(--border); border-top-color: var(--text); border-radius: 50%; }\n@container cw (max-width: 309px) {\n .cw-field { grid-template-columns: repeat(24, minmax(0, 1fr)); }\n .cw-model { max-width: 42%; }\n .cw-compact { padding: 0 8px; }\n .cw-ct { display: none; }\n}\n@container cw (min-width: 520px) { .cw-field { grid-template-columns: repeat(54, minmax(0, 1fr)); } }\n@media (pointer: coarse), (max-width: 639px) { .cw-compact { min-height: 44px; } .cw-part, .cw-mark { min-height: 36px; } }\n";
@@ -0,0 +1,2 @@
1
+ export type { MemoryAddDetail, MemoryClearDetail, MemoryDeleteDetail, MemoryEditDetail, MemoryPanelData, MemoryRestoreDetail, MemoryStore, UsageLevel, } from './memory-panel.js';
2
+ export { entryKeys, entryMatches, FOLD_MS, highlight, isSameEdit, shortText, storeUsed, UiMemoryPanel, UNDO_MS, usageAfterAdd, usageLevel, } from './memory-panel.js';