@2lab.ai/ui 0.0.0-stage → 0.2.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 (60) hide show
  1. package/README.md +55 -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/components/tool-call-card/index.d.ts +7 -0
  35. package/dist/components/tool-call-card/logic.d.ts +87 -0
  36. package/dist/components/tool-call-card/strings.d.ts +94 -0
  37. package/dist/components/tool-call-card/styles.d.ts +5 -0
  38. package/dist/components/tool-call-card/tool-call-card.d.ts +49 -0
  39. package/dist/components/tool-call-card/tool-call-group.d.ts +32 -0
  40. package/dist/context-window.js +384 -0
  41. package/dist/context-window.js.map +1 -0
  42. package/dist/core/element.d.ts +41 -0
  43. package/dist/core/format.d.ts +14 -0
  44. package/dist/core/html.d.ts +10 -0
  45. package/dist/core/tokens.d.ts +14 -0
  46. package/dist/format-BBGWXPzk.js +51 -0
  47. package/dist/format-BBGWXPzk.js.map +1 -0
  48. package/dist/html-elD6Bb_u.js +175 -0
  49. package/dist/html-elD6Bb_u.js.map +1 -0
  50. package/dist/index.d.ts +16 -0
  51. package/dist/index.js +129 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/memory-panel.js +1255 -0
  54. package/dist/memory-panel.js.map +1 -0
  55. package/dist/model-picker.js +858 -0
  56. package/dist/model-picker.js.map +1 -0
  57. package/dist/tokens.css +4 -0
  58. package/dist/tool-call-card.js +1046 -0
  59. package/dist/tool-call-card.js.map +1 -0
  60. package/package.json +79 -4
@@ -0,0 +1,1061 @@
1
+ import { d as define, U as UiElement, p as prefersReducedMotion, e as esc, f as escAttr } from "./html-elD6Bb_u.js";
2
+ import { b as fmtDuration, f as fmtAgo, g as fmtUsd, d as fmtInt } from "./format-BBGWXPzk.js";
3
+ const styles = `
4
+ :host {
5
+ --ib-run-bg: color-mix(in srgb, var(--st-run) 16%, var(--ib-card));
6
+ --ib-run-fg: color-mix(in srgb, var(--st-run) 70%, var(--ib-text));
7
+ --ib-wait-bg: color-mix(in srgb, var(--st-wait) 16%, var(--ib-card));
8
+ --ib-wait-fg: color-mix(in srgb, var(--st-wait) 70%, var(--ib-text));
9
+ --ib-done-bg: color-mix(in srgb, var(--st-done) 16%, var(--ib-card));
10
+ --ib-done-fg: color-mix(in srgb, var(--st-done) 70%, var(--ib-text));
11
+ --ib-fail-bg: color-mix(in srgb, var(--st-fail) 16%, var(--ib-card));
12
+ --ib-fail-fg: color-mix(in srgb, var(--st-fail-text) 70%, var(--ib-text));
13
+ --ib-rest-bg: var(--ib-pill);
14
+ --ib-rest-fg: var(--ib-text-2);
15
+ }
16
+ .ib { container: ib / inline-size; position: relative; }
17
+
18
+ /* Board = equal columns. No overflow clip on columns: a card flying between columns stays visible. */
19
+ .ib-board { display: grid; grid-template-columns: repeat(var(--ib-cols, 3), minmax(0, 1fr)); gap: 12px; align-items: start; }
20
+ .ib-col { position: relative; background: var(--ib-col); border-radius: 14px; padding: 2px 4px 4px; min-width: 0; display: flex; flex-direction: column; }
21
+ .ib-col-head { display: flex; align-items: center; gap: 8px; padding: 10px 8px 8px 10px; min-height: 40px; }
22
+ .ib-col-spark { color: var(--ib-spark); display: inline-flex; flex-shrink: 0; }
23
+ .ib-col-spark svg, .ib-spark svg { width: 14px; height: 14px; }
24
+ .ib-col-name { flex: 1; min-width: 0; margin: 0; text-align: center; font-size: 12px; font-weight: 500; line-height: 1.4; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ib-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
25
+ :host([lang="ko"]) .ib-col-name { letter-spacing: 0.04em; }
26
+ .ib-count { flex-shrink: 0; background: var(--ib-pill); color: var(--ib-text-2); border-radius: 6px; padding: 1px 7px; font-size: 12px; font-weight: 500; min-width: 22px; text-align: center; font-variant-numeric: tabular-nums; }
27
+ .ib-cards { position: relative; display: flex; flex-direction: column; gap: 8px; list-style: none; margin: 0; padding: 0; }
28
+ .ib-empty { margin: 0; text-align: center; color: var(--ib-muted); font-size: 13px; padding: 14px 8px 18px; }
29
+ .ib-empty[hidden] { display: none; }
30
+
31
+ /* List = the same columns stacked full width (same DOM). Forced with view="list"; automatic below 640px. */
32
+ :host([view="list"]) .ib-board { grid-template-columns: minmax(0, 1fr); }
33
+ :host([view="list"]) .ib-col-name { flex: 0 1 auto; text-align: left; }
34
+ :host([view="list"]) .ib-summary { -webkit-line-clamp: 1; }
35
+ :host([view="list"]) .ib-empty { padding: 10px 8px 16px; }
36
+ /* Narrow board: the columns scroll sideways inside the board (the page never scrolls sideways). */
37
+ @container ib (max-width: 839px) {
38
+ :host(:not([view="list"])) .ib-board {
39
+ grid-template-columns: repeat(var(--ib-cols, 3), minmax(min(300px, 84cqi), 1fr));
40
+ overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain; padding-bottom: 8px;
41
+ }
42
+ :host(:not([view="list"])) .ib-col { scroll-snap-align: start; }
43
+ }
44
+ @container ib (max-width: 639px) {
45
+ :host(:not([view="board"])) .ib-board { grid-template-columns: minmax(0, 1fr); overflow-x: visible; scroll-snap-type: none; padding-bottom: 0; }
46
+ :host(:not([view="board"])) .ib-col-name { flex: 0 1 auto; text-align: left; }
47
+ :host(:not([view="board"])) .ib-summary { -webkit-line-clamp: 1; }
48
+ :host(:not([view="board"])) .ib-empty { padding: 10px 8px 16px; }
49
+ }
50
+
51
+ /* ── CARD — collapsed face + in-place expansion ── */
52
+ .ib-card {
53
+ position: relative; background: var(--ib-card); border: 1px solid var(--ib-card-border); border-radius: 12px;
54
+ box-shadow: var(--ib-shadow); color: var(--ib-text); transition: border-color 150ms ease, box-shadow 150ms ease;
55
+ --st-color: var(--ib-muted);
56
+ }
57
+ .ib-card[data-status="waiting"] { --st-color: var(--st-wait); }
58
+ .ib-card[data-status="running"] { --st-color: var(--st-run); }
59
+ .ib-card[data-status="done"] { --st-color: var(--st-done); }
60
+ .ib-card[data-status="failed"] { --st-color: var(--st-fail); }
61
+ /* Parked cards are dimmed with colour, not opacity (opacity would fight the enter / exit fades). */
62
+ .ib-card[data-status="idle"] { background: var(--ib-card-dim); box-shadow: none; }
63
+ .ib-card[data-status="idle"] .ib-title { color: var(--ib-text-2); font-weight: 500; }
64
+ .ib-card[data-status="idle"] .ib-summary { color: var(--ib-muted); }
65
+ .ib-card.is-flying { z-index: 5; box-shadow: var(--ib-shadow-raised); }
66
+ .ib-card.is-exiting { position: absolute; z-index: 1; pointer-events: none; }
67
+ .ib-face { display: block; padding: 12px 14px; border-radius: 11px; container: ibface / inline-size; }
68
+ .ib-face[role="button"] { cursor: pointer; }
69
+ .ib-face:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
70
+ .ib-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
71
+ .ib-spark { flex-shrink: 0; display: inline-flex; color: var(--st-color); }
72
+ .ib-title { flex: 1 1 auto; font-size: 15px; font-weight: 600; line-height: 1.3; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
73
+ .ib-summary {
74
+ margin-top: 6px; font-size: 13.5px; line-height: 1.45; color: var(--ib-text-2);
75
+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere;
76
+ }
77
+ /* The task in progress. */
78
+ .ib-task { margin-top: 8px; display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 13px; color: var(--ib-text); background: var(--ib-pill); border-radius: 8px; padding: 5px 9px; }
79
+ .ib-task-id { flex-shrink: 0; color: var(--ib-muted); font-variant-numeric: tabular-nums; font-size: 12px; }
80
+ .ib-task-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
81
+ .ib-task-time { flex-shrink: 0; color: var(--ib-text-2); font-variant-numeric: tabular-nums; }
82
+ /* Sub-agents: status dots + counts. */
83
+ .ib-agents { margin-top: 8px; display: flex; align-items: center; gap: 7px; min-width: 0; font-size: 12.5px; color: var(--ib-text-2); }
84
+ .ib-agents > svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--ib-muted); }
85
+ .ib-dots { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; }
86
+ .ib-adot { width: 7px; height: 7px; border-radius: 50%; background: var(--ib-muted); display: inline-block; }
87
+ .ib-adot.is-running { background: var(--st-run); animation: ib-pulse 1.6s ease-in-out infinite; }
88
+ .ib-adot.is-done { background: var(--st-done); }
89
+ .ib-adot.is-failed { background: var(--st-fail); }
90
+ .ib-adot.is-unknown { background: transparent; box-shadow: inset 0 0 0 1.5px var(--ib-muted); }
91
+ .ib-dots-more { font-size: 11px; color: var(--ib-muted); margin-left: 2px; font-variant-numeric: tabular-nums; }
92
+ .ib-agents-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
93
+ .ib-agents-text b { color: var(--ib-text); font-weight: 600; }
94
+ .ib-agents-text .ib-fail { color: var(--st-fail-text); font-weight: 600; }
95
+ /* Tokens · cost. */
96
+ .ib-usage { margin-top: 8px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 13px; color: var(--ib-text-2); font-variant-numeric: tabular-nums; }
97
+ .ib-cnt { color: var(--ib-text); font-weight: 600; }
98
+ .ib-unit { margin-left: 3px; color: var(--ib-muted); }
99
+ .ib-sep { color: var(--ib-muted); }
100
+ /* Files the run made: count, newest chips, +N. */
101
+ .ib-arts { display: flex; align-items: center; gap: 5px; margin-top: 8px; min-width: 0; overflow: hidden; }
102
+ .ib-arts-n { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; font-size: 11px; font-weight: 600; color: var(--ib-text-2); font-variant-numeric: tabular-nums; }
103
+ .ib-glyph { width: 14px; height: 14px; flex-shrink: 0; }
104
+ .ib-chip {
105
+ display: inline-flex; align-items: center; gap: 5px; flex: 1 1 0; min-width: 0; max-width: 160px; height: 24px; padding: 0 8px;
106
+ border: 1px solid var(--ib-card-border); border-radius: 999px; background: var(--ib-pill); color: var(--ib-text); font-size: 11.5px; line-height: 1;
107
+ --tint: var(--text-secondary);
108
+ }
109
+ .ib-chip .ib-glyph { color: var(--tint); }
110
+ .ib-chip.is-image { --tint: var(--green); }
111
+ .ib-chip.is-html { --tint: var(--orange); }
112
+ .ib-chip.is-audio { --tint: var(--purple); }
113
+ .ib-chip.is-video, .ib-chip.is-pdf { --tint: var(--red); }
114
+ .ib-chip.is-markdown { --tint: var(--accent); }
115
+ .ib-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
116
+ .ib-arts-more { flex-shrink: 0; font-size: 11px; color: var(--ib-muted); }
117
+ /* Foot: the status tag, then the metrics on the right (a narrow foot wraps them below). */
118
+ .ib-foot { margin-top: 10px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; font-size: 13px; color: var(--ib-text-2); min-width: 0; }
119
+ .ib-tag {
120
+ display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; flex: 0 1 auto; height: 24px; padding: 0 10px 0 8px;
121
+ border-radius: 999px; font-size: 12.5px; font-weight: 500; white-space: nowrap; overflow: hidden; font-variant-numeric: tabular-nums;
122
+ }
123
+ .ib-tag[data-tone="run"] { background: var(--ib-run-bg); color: var(--ib-run-fg); }
124
+ .ib-tag[data-tone="wait"] { background: var(--ib-wait-bg); color: var(--ib-wait-fg); }
125
+ .ib-tag[data-tone="done"] { background: var(--ib-done-bg); color: var(--ib-done-fg); }
126
+ .ib-tag[data-tone="fail"] { background: var(--ib-fail-bg); color: var(--ib-fail-fg); }
127
+ .ib-tag[data-tone="rest"] { background: var(--ib-rest-bg); color: var(--ib-rest-fg); }
128
+ .ib-tag-text { overflow: hidden; text-overflow: ellipsis; }
129
+ .ib-tag-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: currentColor; }
130
+ .ib-tag-icon svg { width: 12px; height: 12px; }
131
+ .ib-ago { color: inherit; }
132
+ .ib-ago::before { content: "·"; margin: 0 6px; }
133
+ /* The state word ("Running · ", "Done in ") goes only when the face has 300px of content or less. */
134
+ @container ibface (max-width: 300px) { .ib-tag .ib-pre { display: none; } }
135
+ .ib-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; display: inline-block; }
136
+ .ib-dot.is-hollow { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }
137
+ /* The running ring: its track follows the tag colour, so it reads on the tint. */
138
+ .ib-spin {
139
+ width: 12px; height: 12px; border-radius: 50%; display: inline-block;
140
+ border: 2px solid color-mix(in srgb, currentColor 25%, transparent); border-top-color: currentColor; border-right-color: currentColor;
141
+ animation: ib-spin 0.9s linear infinite;
142
+ }
143
+ .ib-metrics { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; row-gap: 4px; max-width: 100%; color: var(--ib-muted); flex-shrink: 0; margin-left: auto; font-variant-numeric: tabular-nums; }
144
+ .ib-metric { display: inline-flex; align-items: center; gap: 5px; padding: 0 9px; }
145
+ .ib-metric + .ib-metric { border-left: 1px solid var(--ib-card-border); }
146
+ .ib-metric:last-child { padding-right: 0; }
147
+ .ib-metric svg { width: 13px; height: 13px; }
148
+ .ib-metric.is-warn svg { color: var(--st-wait); }
149
+ .ib-metric.is-danger { color: var(--st-fail-text); font-weight: 600; }
150
+
151
+ /* Expansion — grid row 0fr → 1fr; cards below ride the animated row. */
152
+ .ib-expand { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-expand) var(--ease-out); }
153
+ .ib-card.is-open .ib-expand { grid-template-rows: 1fr; }
154
+ .ib-expand-inner { min-height: 0; overflow: hidden; opacity: 0; transition: opacity var(--dur-expand) var(--ease-out); }
155
+ .ib-card.is-open .ib-expand-inner { opacity: 1; }
156
+ .ib-card.no-motion .ib-expand, .ib-card.no-motion .ib-expand-inner { transition: none; }
157
+ .ib-body { margin: 0 14px; padding: 12px 0 14px; border-top: 1px solid var(--ib-card-border); font-size: 13px; color: var(--ib-text-2); }
158
+ /* Steps: dot + connector, the current step's dot in ink. */
159
+ .ib-more { font-size: 12px; color: var(--ib-muted); padding: 0 0 6px 16px; }
160
+ .ib-steps { list-style: none; margin: 0; padding: 0; }
161
+ .ib-step { position: relative; display: flex; gap: 10px; padding: 0 0 8px 16px; line-height: 1.4; color: var(--ib-text-2); }
162
+ .ib-step::before { content: ''; position: absolute; left: 2px; top: 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--ib-muted); opacity: 0.55; }
163
+ .ib-step::after { content: ''; position: absolute; left: 4.5px; top: 15px; bottom: 1px; width: 1px; background: var(--ib-card-border); }
164
+ .ib-step:last-child { padding-bottom: 0; }
165
+ .ib-step:last-child::after { display: none; }
166
+ .ib-step.is-current { color: var(--ib-text); }
167
+ .ib-step.is-current::before { background: var(--ib-text); opacity: 1; }
168
+ .ib-step.is-pending { color: var(--ib-muted); }
169
+ .ib-step-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
170
+ .ib-step-meta { flex-shrink: 0; color: var(--ib-muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }
171
+ /* Button row: model on the left, secondary outline buttons, one ink primary last. */
172
+ .ib-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--ib-card-border); }
173
+ .ib-bar:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
174
+ .ib-model { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ib-muted); font-size: 13px; }
175
+ .ib-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; margin-left: auto; }
176
+ .ib-btn {
177
+ display: inline-flex; align-items: center; justify-content: center; background: var(--ib-card); border: 1px solid var(--ib-card-border);
178
+ border-radius: 8px; color: var(--ib-text); font: inherit; font-size: 13px; font-weight: 500; padding: 5px 12px; min-height: 32px;
179
+ white-space: nowrap; cursor: pointer;
180
+ transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease, opacity 150ms ease, transform var(--dur-press) var(--ease-out);
181
+ }
182
+ .ib-btn.is-primary { background: var(--ib-text); border-color: var(--ib-text); color: var(--ib-card); }
183
+ .ib-btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
184
+ .ib-btn:active:not([aria-disabled="true"]):not(:focus-visible) { transform: scale(0.97); }
185
+
186
+ @keyframes ib-spin { to { transform: rotate(360deg); } }
187
+ @keyframes ib-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
188
+
189
+ /* Hover — pointer devices only; colour / border, never movement. */
190
+ @media (hover: hover) and (pointer: fine) {
191
+ .ib-card:hover { border-color: var(--ib-muted); }
192
+ .ib-card[data-status="idle"]:hover { border-color: var(--ib-card-border); }
193
+ .ib-btn:hover:not([aria-disabled="true"]) { border-color: var(--ib-muted); }
194
+ .ib-btn.is-primary:hover:not([aria-disabled="true"]) { border-color: var(--ib-text); opacity: 0.88; }
195
+ }
196
+ /* 44px targets on touch and narrow screens. */
197
+ @media (pointer: coarse), (max-width: 639px) {
198
+ .ib-btn { min-height: 44px; padding: 8px 14px; }
199
+ }
200
+ /* Reduced motion: the global rule only shortens durations, so infinite loops stop here; the ring stays whole. */
201
+ @media (prefers-reduced-motion: reduce) {
202
+ .ib-spin { animation: none; border-color: currentColor; }
203
+ .ib-adot.is-running { animation: none; }
204
+ }
205
+ `;
206
+ const BOARD_HOLD_MS = 900;
207
+ const ANNOUNCE_MS = 1e3;
208
+ const MAX_DOTS = 6;
209
+ const MAX_CHIPS = 4;
210
+ const MAX_DETAILS = 5;
211
+ const COUNT_MS = 900;
212
+ const STATUSES = ["waiting", "running", "done", "failed", "idle"];
213
+ function agoKo(at, now = Date.now()) {
214
+ const s = Math.max(0, Math.floor((now - at) / 1e3));
215
+ if (s < 45) return "방금";
216
+ const m = Math.floor(s / 60);
217
+ if (m < 60) return `${Math.max(1, m)}분 전`;
218
+ const h = Math.floor(m / 60);
219
+ if (h < 48) return `${h}시간 전`;
220
+ return `${Math.floor(h / 24)}일 전`;
221
+ }
222
+ const STRINGS = {
223
+ en: {
224
+ status: {
225
+ waiting: "Waiting for you",
226
+ running: "Running",
227
+ done: "Done",
228
+ failed: "Failed",
229
+ idle: "Idle"
230
+ },
231
+ doneIn: "Done in ",
232
+ empty: "Nothing here",
233
+ open: "Open",
234
+ columns: { needs: "Needs you", running: "Running", done: "Done" },
235
+ agents: "Sub-agents",
236
+ agentsRunning: (n) => `running ${n}`,
237
+ agentsFailed: (n) => `failed ${n}`,
238
+ tok: "tok",
239
+ tokTitle: "Total tokens",
240
+ costTitle: "API-equivalent cost",
241
+ steps: "Tasks done / total",
242
+ ctx: (p) => `Context window used — ${p}%`,
243
+ arts: "Files the run made",
244
+ earlier: (n) => `+${n} earlier`,
245
+ announce: (w, r) => `${w} need${w === 1 ? "s" : ""} you, ${r} running`,
246
+ changed: (title, word) => `${title}: ${word}.`,
247
+ ago: (at, now) => fmtAgo(at, now)
248
+ },
249
+ ko: {
250
+ status: {
251
+ waiting: "입력 대기",
252
+ running: "실행 중",
253
+ done: "완료",
254
+ failed: "실패",
255
+ idle: "대기"
256
+ },
257
+ doneIn: "완료 · ",
258
+ empty: "비어 있음",
259
+ open: "열기",
260
+ columns: { needs: "응답 필요", running: "실행 중", done: "완료" },
261
+ agents: "서브에이전트",
262
+ agentsRunning: (n) => `실행 ${n}`,
263
+ agentsFailed: (n) => `실패 ${n}`,
264
+ tok: "tok",
265
+ tokTitle: "총 토큰",
266
+ costTitle: "API 환산 비용",
267
+ steps: "완료한 task / 전체",
268
+ ctx: (p) => `컨텍스트 ${p}% 사용`,
269
+ arts: "모델이 만든 산출물",
270
+ earlier: (n) => `이전 ${n}개 더`,
271
+ announce: (w, r) => `응답이 필요한 세션 ${w}개, 실행 중 ${r}개`,
272
+ changed: (title, word) => `${title}: ${word}.`,
273
+ ago: agoKo
274
+ }
275
+ };
276
+ const SPARK_SVG = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M7 1.5c.5 3.3 2.2 5 5.5 5.5-3.3.5-5 2.2-5.5 5.5C6.5 9.2 4.8 7.5 1.5 7c3.3-.5 5-2.2 5.5-5.5z"/><circle cx="13" cy="12.6" r="1.3"/></svg>';
277
+ const WAIT_SVG = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1.8 15 14H1z"/></svg>';
278
+ const FAIL_SVG = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zm-.8 3h1.6v4.8H7.2zM8 12.3a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>';
279
+ const TASKS_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 4.2l1.4 1.4 2.4-2.6M2.5 10.2l1.4 1.4 2.4-2.6M9 4.5h4.5M9 10.5h4.5"/></svg>';
280
+ const CTX_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><circle cx="8" cy="8" r="5.6"/><path d="M8 8V2.4A5.6 5.6 0 0 1 13.6 8z" fill="currentColor" stroke="none"/></svg>';
281
+ const AGENTS_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="4" cy="3.5" r="1.8"/><circle cx="12" cy="8" r="1.8"/><circle cx="12" cy="13" r="1.8"/><path d="M4 5.3v5.2c0 1.4 1.1 2.5 2.5 2.5h3.7M4 8h6.2"/></svg>';
282
+ const ART_GLYPH = {
283
+ image: '<rect x="2" y="3" width="12" height="10" rx="1.5"/><circle cx="6" cy="6.5" r="1.2"/><path d="M14 11l-3.5-3.5L4 13"/>',
284
+ audio: '<path d="M2.5 7.5v1M5 5.5v5M7.5 3v10M10 5v6M12.5 6.5v3"/>',
285
+ video: '<rect x="2" y="4" width="9" height="8" rx="1.5"/><path d="M11 7l3-2v6l-3-2z"/>',
286
+ html: '<path d="M5.5 5L2.5 8l3 3M10.5 5l3 3-3 3M9 3.5l-2 9"/>',
287
+ markdown: '<path d="M4 2h5l3 3v9H4z"/><path d="M6 8h4M6 10.5h4M6 5.5h1.5"/>',
288
+ pdf: '<path d="M4 2h5l3 3v9H4z"/><path d="M9 2v3h3M6 10h4"/>',
289
+ text: '<path d="M3 4h10M3 7h10M3 10h7M3 13h5"/>',
290
+ file: '<path d="M4 2h5l3 3v9H4z"/><path d="M9 2v3h3"/>'
291
+ };
292
+ const glyph = (k) => `<svg class="ib-glyph" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ART_GLYPH[k]}</svg>`;
293
+ const num = (v) => {
294
+ const n = Number(v);
295
+ return Number.isFinite(n) && n > 0 ? n : 0;
296
+ };
297
+ const finite = (v) => typeof v === "number" && Number.isFinite(v);
298
+ function statusOf(card) {
299
+ return STATUSES.includes(card?.status) ? card.status : "idle";
300
+ }
301
+ function statusTone(status) {
302
+ return status === "waiting" ? "wait" : status === "running" ? "run" : status === "done" ? "done" : status === "failed" ? "fail" : "rest";
303
+ }
304
+ function artifactKind(name) {
305
+ const m = /\.([a-z0-9]{1,6})$/i.exec(String(name ?? "").trim());
306
+ const ext = m ? m[1].toLowerCase() : "";
307
+ if (/^(png|jpe?g|gif|webp|svg|avif|bmp)$/.test(ext)) return "image";
308
+ if (/^html?$/.test(ext)) return "html";
309
+ if (/^(md|mdx|markdown)$/.test(ext)) return "markdown";
310
+ if (ext === "pdf") return "pdf";
311
+ if (/^(mp3|wav|m4a|ogg|flac|aac)$/.test(ext)) return "audio";
312
+ if (/^(mp4|mov|webm|mkv)$/.test(ext)) return "video";
313
+ if (/^(txt|log|csv|json|ya?ml|xml)$/.test(ext)) return "text";
314
+ return "file";
315
+ }
316
+ function agentDots(a, max = MAX_DOTS) {
317
+ const total = Math.floor(num(a?.total));
318
+ if (!total) return { dots: [], more: 0 };
319
+ const running = Math.min(total, Math.floor(num(a?.running)));
320
+ const failed = Math.min(total - running, Math.floor(num(a?.failed)));
321
+ const done = Math.min(total - running - failed, Math.floor(num(a?.done)));
322
+ const all = [
323
+ ...Array(running).fill("running"),
324
+ ...Array(failed).fill("failed"),
325
+ ...Array(done).fill("done"),
326
+ ...Array(total - running - failed - done).fill("unknown")
327
+ ];
328
+ return { dots: all.slice(0, max), more: Math.max(0, total - max) };
329
+ }
330
+ function boardCounts(columns) {
331
+ const seen = /* @__PURE__ */ new Set();
332
+ let needs = 0;
333
+ let running = 0;
334
+ for (const col of Array.isArray(columns) ? columns : []) {
335
+ for (const c of Array.isArray(col?.cards) ? col.cards : []) {
336
+ if (!c || seen.has(String(c.id))) continue;
337
+ seen.add(String(c.id));
338
+ const s = statusOf(c);
339
+ if (s === "waiting" || s === "failed") needs++;
340
+ else if (s === "running") running++;
341
+ }
342
+ }
343
+ return { needs, running };
344
+ }
345
+ function columnsByStatus(cards, lang = "en") {
346
+ const L = STRINGS[lang === "ko" ? "ko" : "en"].columns;
347
+ const list = Array.isArray(cards) ? cards : [];
348
+ const of = (...s) => list.filter((c) => c && s.includes(statusOf(c)));
349
+ return [
350
+ { id: "needs-you", label: L.needs, cards: of("waiting", "failed") },
351
+ { id: "running", label: L.running, cards: of("running") },
352
+ { id: "done", label: L.done, cards: of("done", "idle") }
353
+ ];
354
+ }
355
+ function holdLanes(want, drawn, holds, now, on) {
356
+ const lanes = /* @__PURE__ */ Object.create(null);
357
+ const next = /* @__PURE__ */ Object.create(null);
358
+ let wakeAt = 0;
359
+ for (const key of Object.keys(want)) {
360
+ const to = want[key];
361
+ const from = drawn[key];
362
+ lanes[key] = to;
363
+ if (!on || !from || from === to) continue;
364
+ const h = holds[key];
365
+ const until = h && h.from === from ? h.until : now + BOARD_HOLD_MS;
366
+ if (now >= until) continue;
367
+ lanes[key] = from;
368
+ next[key] = { from, to, until };
369
+ if (!wakeAt || until < wakeAt) wakeAt = until;
370
+ }
371
+ return { lanes, holds: next, wakeAt };
372
+ }
373
+ function placeHolds(slots, lanes, drawnOrder) {
374
+ const out = /* @__PURE__ */ Object.create(null);
375
+ for (const k of Object.keys(slots)) out[k] = slots[k].slice();
376
+ const moves = [];
377
+ const dropped = [];
378
+ for (const col of Object.keys(out)) {
379
+ const list = out[col];
380
+ for (let i = list.length - 1; i >= 0; i--) {
381
+ const id = list[i];
382
+ const lane = lanes[id];
383
+ if (!lane || lane === col) continue;
384
+ if (!out[lane]) {
385
+ dropped.push(id);
386
+ continue;
387
+ }
388
+ list.splice(i, 1);
389
+ moves.push({ id, home: lane, at: Math.max(0, (drawnOrder[lane] ?? []).indexOf(id)) });
390
+ }
391
+ }
392
+ moves.sort((a, b) => a.at - b.at);
393
+ for (const m of moves) {
394
+ const l = out[m.home];
395
+ l.splice(Math.min(m.at, l.length), 0, m.id);
396
+ }
397
+ return { slots: out, dropped };
398
+ }
399
+ function fmtCount(kind, v) {
400
+ return kind === "usd" ? fmtUsd(v) : fmtInt(v);
401
+ }
402
+ function statusWord(c, t) {
403
+ const own = typeof c.statusText === "string" ? c.statusText.trim() : "";
404
+ return own || t.status[statusOf(c)];
405
+ }
406
+ function renderStatus(c, t) {
407
+ const s = statusOf(c);
408
+ const word = statusWord(c, t);
409
+ const since = num(c.since);
410
+ const acc = num(c.elapsedMs);
411
+ const ago = since ? `<span class="ib-ago" data-at="${since}"></span>` : "";
412
+ const icon = (inner) => `<span class="ib-tag-icon" aria-hidden="true">${inner}</span>`;
413
+ if (s === "running") {
414
+ const text = since || acc ? `<span class="ib-pre">${esc(word)} · </span><span class="ib-clock" data-clock data-from="${since}" data-acc="${acc}"></span>` : esc(word);
415
+ return `${icon('<span class="ib-spin"></span>')}<span class="ib-tag-text">${text}</span>`;
416
+ }
417
+ if (s === "waiting") return `${icon(WAIT_SVG)}<span class="ib-tag-text">${esc(word)}${ago}</span>`;
418
+ if (s === "failed") return `${icon(FAIL_SVG)}<span class="ib-tag-text">${esc(word)}${ago}</span>`;
419
+ if (s === "done") {
420
+ const pre = c.statusText?.trim() ? `${esc(word)} · ` : esc(t.doneIn);
421
+ const text = acc ? `<span class="ib-pre">${pre}</span>${esc(fmtDuration(acc))}` : esc(word);
422
+ return `${icon('<span class="ib-dot"></span>')}<span class="ib-tag-text">${text}${ago}</span>`;
423
+ }
424
+ return `${icon('<span class="ib-dot is-hollow"></span>')}<span class="ib-tag-text">${esc(word)}${ago}</span>`;
425
+ }
426
+ function renderTask(c) {
427
+ const task = c.task;
428
+ if (!task || typeof task.label !== "string" || !task.label) return "";
429
+ const from = num(task.startedAt);
430
+ const fixed = num(task.elapsedMs);
431
+ const time = from ? `<span class="ib-task-time" data-clock data-from="${from}" data-acc="0"></span>` : fixed ? `<span class="ib-task-time">${esc(fmtDuration(fixed))}</span>` : "";
432
+ return `<div class="ib-task" title="${escAttr(task.label)}">${task.id ? `<span class="ib-task-id">#${esc(task.id)}</span>` : ""}<span class="ib-task-name">${esc(task.label)}</span>${time}</div>`;
433
+ }
434
+ function renderAgents(c, t) {
435
+ const { dots, more } = agentDots(c.agents);
436
+ if (!dots.length) return "";
437
+ const total = Math.floor(num(c.agents?.total));
438
+ const running = Math.min(total, Math.floor(num(c.agents?.running)));
439
+ const failed = Math.floor(num(c.agents?.failed));
440
+ const parts = [`${esc(t.agents)} <b>${total}</b>`];
441
+ if (running) parts.push(esc(t.agentsRunning(running)));
442
+ if (failed) parts.push(`<span class="ib-fail">${esc(t.agentsFailed(failed))}</span>`);
443
+ const words = [`${t.agents} ${total}`, running ? t.agentsRunning(running) : "", failed ? t.agentsFailed(failed) : ""].filter(Boolean).join(", ");
444
+ return `<div class="ib-agents" title="${escAttr(words)}">${AGENTS_SVG}<span class="ib-dots" aria-hidden="true">${dots.map((d) => `<i class="ib-adot is-${d}"></i>`).join(
445
+ ""
446
+ )}${more ? `<span class="ib-dots-more">+${more}</span>` : ""}</span><span class="ib-agents-text">${parts.join(" · ")}</span></div>`;
447
+ }
448
+ function renderUsage(c, t) {
449
+ const parts = [];
450
+ if (finite(c.tokens) && c.tokens >= 0)
451
+ parts.push(
452
+ `<span class="ib-use" title="${escAttr(t.tokTitle)}"><span class="ib-cnt" data-cnt="tok" data-to="${c.tokens}">${fmtInt(c.tokens)}</span><span class="ib-unit">${esc(t.tok)}</span></span>`
453
+ );
454
+ if (finite(c.costUsd) && c.costUsd >= 0)
455
+ parts.push(
456
+ `<span class="ib-use" title="${escAttr(t.costTitle)}"><span class="ib-cnt" data-cnt="usd" data-to="${c.costUsd}">${fmtUsd(c.costUsd)}</span></span>`
457
+ );
458
+ return parts.length ? `<div class="ib-usage">${parts.join('<span class="ib-sep" aria-hidden="true">·</span>')}</div>` : "";
459
+ }
460
+ function renderArtifacts(c, t) {
461
+ const names = (Array.isArray(c.artifacts) ? c.artifacts : []).filter((n) => typeof n === "string" && n);
462
+ if (!names.length) return "";
463
+ const shown = names.slice(0, MAX_CHIPS);
464
+ const more = names.length - shown.length;
465
+ return `<div class="ib-arts"><span class="ib-arts-n" title="${escAttr(t.arts)}">${glyph("file")}${names.length}</span>${shown.map((n) => {
466
+ const k = artifactKind(n);
467
+ return `<span class="ib-chip is-${k}" title="${escAttr(n)}">${glyph(k)}<span class="ib-chip-name">${esc(n)}</span></span>`;
468
+ }).join("")}${more > 0 ? `<span class="ib-arts-more">+${more}</span>` : ""}</div>`;
469
+ }
470
+ function renderMetrics(c, t) {
471
+ const out = [];
472
+ const total = Math.floor(num(c.steps?.total));
473
+ if (total) {
474
+ const done = Math.min(total, Math.floor(num(c.steps?.done)));
475
+ out.push(`<span class="ib-metric" title="${escAttr(t.steps)}">${TASKS_SVG}${done}/${total}</span>`);
476
+ }
477
+ const pct = num(c.contextPct);
478
+ if (pct) {
479
+ const p = Math.round(Math.min(pct, 100));
480
+ const lv = pct > 80 ? " is-danger" : pct > 60 ? " is-warn" : "";
481
+ out.push(`<span class="ib-metric${lv}" title="${escAttr(t.ctx(p))}">${CTX_SVG}${p}%</span>`);
482
+ }
483
+ return out.length ? `<span class="ib-metrics">${out.join("")}</span>` : "";
484
+ }
485
+ function renderFace(c, t = STRINGS.en) {
486
+ return `<div class="ib-head"><span class="ib-spark" aria-hidden="true">${SPARK_SVG}</span><span class="ib-title">${esc(c.title)}</span></div>${c.summary ? `<div class="ib-summary">${esc(c.summary)}</div>` : ""}${renderTask(c)}${renderAgents(c, t)}${renderUsage(c, t)}${renderArtifacts(c, t)}<div class="ib-foot"><span class="ib-tag" data-tone="${statusTone(
487
+ statusOf(c)
488
+ )}">${renderStatus(c, t)}</span>${renderMetrics(c, t)}</div>`;
489
+ }
490
+ function renderButton(cardId, a, primary) {
491
+ const why = typeof a.disabled === "string" ? a.disabled : "";
492
+ const off = !!a.disabled;
493
+ return `<button type="button" class="ib-btn${primary ? " is-primary" : ""}" data-act="${escAttr(a.id)}" data-key="${escAttr(
494
+ `a:${cardId}:${a.id}`
495
+ )}"${off ? ' aria-disabled="true"' : ""}${why ? ` title="${escAttr(why)}"` : ""}>${esc(a.label)}${why ? `<span class="sr-only"> — ${esc(why)}</span>` : ""}</button>`;
496
+ }
497
+ function renderBody(c, t = STRINGS.en, openLabel = t.open) {
498
+ const id = String(c.id);
499
+ const raw = (Array.isArray(c.details) ? c.details : []).map((d) => typeof d === "string" ? { label: d } : d).filter((d) => !!d && typeof d.label === "string" && d.label !== "");
500
+ let steps = "";
501
+ if (raw.length) {
502
+ const folded = Math.max(0, raw.length - MAX_DETAILS);
503
+ const shown = raw.slice(folded);
504
+ steps = `<div class="ib-steps-wrap">${folded ? `<div class="ib-more">${esc(t.earlier(folded))}</div>` : ""}<ol class="ib-steps">${shown.map((d, i) => {
505
+ const state = d.state ?? (i === shown.length - 1 ? "current" : "done");
506
+ return `<li class="ib-step is-${state === "current" || state === "pending" ? state : "done"}"><span class="ib-step-text">${esc(
507
+ d.label
508
+ )}</span>${d.meta ? `<span class="ib-step-meta">${esc(d.meta)}</span>` : ""}</li>`;
509
+ }).join("")}</ol></div>`;
510
+ }
511
+ const primary = c.primary && c.primary.id != null && c.primary.label ? c.primary : null;
512
+ const decides = !!primary && !primary.disabled;
513
+ const btns = [];
514
+ for (const a of Array.isArray(c.actions) ? c.actions : [])
515
+ if (a && a.id != null && a.label) btns.push(renderButton(id, a, false));
516
+ if (openLabel)
517
+ btns.push(
518
+ `<button type="button" class="ib-btn${decides ? "" : " is-primary"}" data-open data-key="${escAttr(`o:${id}`)}">${esc(openLabel)}</button>`
519
+ );
520
+ if (primary) btns.push(renderButton(id, primary, decides));
521
+ const model = c.model ? `<span class="ib-model">${esc(c.model)}</span>` : "";
522
+ const bar = btns.length || model ? `<div class="ib-bar">${model}<span class="ib-actions">${btns.join("")}</span></div>` : "";
523
+ return steps || bar ? `<div class="ib-body">${steps}${bar}</div>` : "";
524
+ }
525
+ class UiAgentInbox extends UiElement {
526
+ static styles = [styles];
527
+ static get observedAttributes() {
528
+ return ["lang"];
529
+ }
530
+ #wrap = null;
531
+ #board = null;
532
+ #live = null;
533
+ #cards = /* @__PURE__ */ new Map();
534
+ #cols = /* @__PURE__ */ new Map();
535
+ #order = /* @__PURE__ */ Object.create(null);
536
+ #open = /* @__PURE__ */ new Set();
537
+ #holds = /* @__PURE__ */ Object.create(null);
538
+ #lastStatus = /* @__PURE__ */ new Map();
539
+ #seq = 0;
540
+ #painted = false;
541
+ #holdTimer;
542
+ #tickTimer;
543
+ #announceTimer;
544
+ #announcePending = "";
545
+ #lastCounts = "";
546
+ #shown = /* @__PURE__ */ new Map();
547
+ #counting = /* @__PURE__ */ new Map();
548
+ #raf = 0;
549
+ attributeChangedCallback() {
550
+ this.requestRender();
551
+ }
552
+ get #lang() {
553
+ return this.getAttribute("lang") === "ko" ? "ko" : "en";
554
+ }
555
+ get #t() {
556
+ return STRINGS[this.#lang];
557
+ }
558
+ constructor() {
559
+ super();
560
+ this.root.addEventListener("click", (e) => this.#onClick(e));
561
+ this.root.addEventListener("keydown", (e) => this.#onKey(e));
562
+ }
563
+ disconnectedCallback() {
564
+ clearTimeout(this.#holdTimer);
565
+ clearTimeout(this.#announceTimer);
566
+ clearInterval(this.#tickTimer);
567
+ if (this.#raf && typeof cancelAnimationFrame === "function") cancelAnimationFrame(this.#raf);
568
+ this.#holdTimer = this.#announceTimer = this.#tickTimer = void 0;
569
+ this.#raf = 0;
570
+ for (const [key, c] of this.#counting) {
571
+ c.el.textContent = fmtCount(c.kind, c.to);
572
+ this.#shown.set(key, c.to);
573
+ }
574
+ this.#counting.clear();
575
+ this.#holds = /* @__PURE__ */ Object.create(null);
576
+ }
577
+ /** The board is reconciled by `update()` (keyed, persistent nodes); there is no single markup string. */
578
+ render() {
579
+ return "";
580
+ }
581
+ // ── Events ──
582
+ #cardOf(el) {
583
+ const li = el?.closest?.("li.ib-card[data-card]");
584
+ return li ? li.getAttribute("data-card") ?? null : null;
585
+ }
586
+ #onClick(e) {
587
+ const target = e.target;
588
+ const act = target?.closest?.("[data-act]");
589
+ if (act) {
590
+ const id = this.#cardOf(act);
591
+ if (id === null || act.getAttribute("aria-disabled") === "true") return;
592
+ this.emit("ui-action", { cardId: id, actionId: act.getAttribute("data-act") ?? "" });
593
+ return;
594
+ }
595
+ const open = target?.closest?.("[data-open]");
596
+ if (open) {
597
+ const id = this.#cardOf(open);
598
+ if (id !== null) this.emit("ui-open", { cardId: id });
599
+ return;
600
+ }
601
+ const face = target?.closest?.('.ib-face[role="button"]');
602
+ if (face) {
603
+ const id = this.#cardOf(face);
604
+ if (id !== null) this.#toggle(id, false);
605
+ }
606
+ }
607
+ #onKey(e) {
608
+ const target = e.target;
609
+ if (!target) return;
610
+ if ((e.key === "Enter" || e.key === " ") && target.matches?.('.ib-face[role="button"]')) {
611
+ e.preventDefault();
612
+ if (e.repeat) return;
613
+ const id2 = this.#cardOf(target);
614
+ if (id2 !== null) this.#toggle(id2, true);
615
+ return;
616
+ }
617
+ if (e.key !== "Escape") return;
618
+ const tag = String(target.tagName || "").toUpperCase();
619
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable) return;
620
+ const li = target.closest?.("li.ib-card.is-open");
621
+ const id = li ? li.getAttribute("data-card") : null;
622
+ if (id === null || !this.#open.has(id)) return;
623
+ e.stopPropagation();
624
+ if (this.#toggle(id, true)) this.#cards.get(id)?.face.focus({ preventScroll: true });
625
+ }
626
+ /** Open / close one card in place. Keyboard toggles skip the transition. Returns false when the host prevented it. */
627
+ #toggle(id, instant) {
628
+ const sh = this.#cards.get(id);
629
+ if (!sh || !sh.face.hasAttribute("aria-controls")) return false;
630
+ const open = !this.#open.has(id);
631
+ if (!this.emit("ui-toggle", { cardId: id, expanded: open })) return false;
632
+ if (open) this.#open.add(id);
633
+ else this.#open.delete(id);
634
+ if (instant) sh.el.classList.add("no-motion");
635
+ this.#applyOpen(sh, open);
636
+ if (instant) {
637
+ void sh.el.offsetHeight;
638
+ sh.el.classList.remove("no-motion");
639
+ }
640
+ return true;
641
+ }
642
+ #applyOpen(sh, open) {
643
+ if (sh.el.classList.contains("is-open") !== open) sh.el.classList.toggle("is-open", open);
644
+ if (sh.face.hasAttribute("aria-controls")) sh.face.setAttribute("aria-expanded", String(open));
645
+ sh.expand.toggleAttribute("inert", !open);
646
+ }
647
+ // ── Reconciler ──
648
+ #skeleton() {
649
+ if (this.#wrap) return;
650
+ const wrap = document.createElement("div");
651
+ wrap.className = "ib";
652
+ const live = document.createElement("p");
653
+ live.className = "sr-only";
654
+ live.setAttribute("role", "status");
655
+ live.setAttribute("aria-live", "polite");
656
+ live.setAttribute("aria-atomic", "true");
657
+ const board = document.createElement("div");
658
+ board.className = "ib-board";
659
+ wrap.append(live, board);
660
+ this.content.append(wrap);
661
+ this.#wrap = wrap;
662
+ this.#live = live;
663
+ this.#board = board;
664
+ }
665
+ #colShell(id) {
666
+ const n = ++this.#seq;
667
+ const el = document.createElement("section");
668
+ el.className = "ib-col";
669
+ el.setAttribute("data-col", id);
670
+ const head = document.createElement("div");
671
+ head.className = "ib-col-head";
672
+ head.innerHTML = `<span class="ib-col-spark" aria-hidden="true">${SPARK_SVG}</span>`;
673
+ const name = document.createElement("h3");
674
+ name.className = "ib-col-name";
675
+ name.id = `ib-c${n}-name`;
676
+ const count = document.createElement("span");
677
+ count.className = "ib-count";
678
+ count.id = `ib-c${n}-count`;
679
+ head.append(name, count);
680
+ const list = document.createElement("ul");
681
+ list.className = "ib-cards";
682
+ list.setAttribute("role", "list");
683
+ const empty = document.createElement("p");
684
+ empty.className = "ib-empty";
685
+ el.setAttribute("aria-labelledby", `${name.id} ${count.id}`);
686
+ el.append(head, list, empty);
687
+ return { el, name, count, list, empty };
688
+ }
689
+ #shell(id) {
690
+ const el = document.createElement("li");
691
+ el.className = "ib-card";
692
+ el.setAttribute("data-card", id);
693
+ const face = document.createElement("div");
694
+ face.className = "ib-face";
695
+ face.setAttribute("data-key", `f:${id}`);
696
+ const expand = document.createElement("div");
697
+ expand.className = "ib-expand";
698
+ expand.id = `ib-x${++this.#seq}`;
699
+ expand.setAttribute("inert", "");
700
+ const body = document.createElement("div");
701
+ body.className = "ib-expand-inner";
702
+ expand.append(body);
703
+ el.append(face, expand);
704
+ return { el, face, expand, body, faceHtml: null, bodyHtml: null, col: "", status: "", flip: null };
705
+ }
706
+ #motion() {
707
+ const cs = typeof getComputedStyle === "function" ? getComputedStyle(this) : null;
708
+ const ms = (name, fallback) => {
709
+ const raw = cs?.getPropertyValue(name).trim() ?? "";
710
+ const v = Number.parseFloat(raw);
711
+ if (!Number.isFinite(v)) return fallback;
712
+ return /[^m]s$/.test(raw) ? v * 1e3 : v;
713
+ };
714
+ const str = (name, fallback) => cs?.getPropertyValue(name).trim() || fallback;
715
+ return {
716
+ move: ms("--dur-move", 320),
717
+ fade: ms("--dur-fade", 180),
718
+ easeOut: str("--ease-out", "cubic-bezier(0.23, 1, 0.32, 1)"),
719
+ easeInOut: str("--ease-in-out", "cubic-bezier(0.77, 0, 0.175, 1)")
720
+ };
721
+ }
722
+ /** Top-left of the card's full-size box as drawn (a move in flight included, an entrance scale not). */
723
+ #boxOf(el) {
724
+ const r = el.getBoundingClientRect();
725
+ return { x: r.left + r.width / 2 - el.offsetWidth / 2, y: r.top + r.height / 2 - el.offsetHeight / 2 };
726
+ }
727
+ update() {
728
+ this.#skeleton();
729
+ const board = this.#board;
730
+ const t = this.#t;
731
+ const d = this.data;
732
+ const now = Date.now();
733
+ const activeKey = this.root.activeElement?.dataset?.key;
734
+ const hidden = typeof document !== "undefined" && document.hidden;
735
+ const reduced = prefersReducedMotion();
736
+ const live = this.#painted && !hidden;
737
+ const canAnimate = live && typeof HTMLElement.prototype.animate === "function";
738
+ const openLabel = d?.openLabel === false ? false : typeof d?.openLabel === "string" ? d.openLabel : t.open;
739
+ const startOpen = new Set((Array.isArray(d?.expanded) ? d.expanded : []).map(String));
740
+ const cols = [];
741
+ const cardById = /* @__PURE__ */ new Map();
742
+ const slots = /* @__PURE__ */ Object.create(null);
743
+ const want = /* @__PURE__ */ Object.create(null);
744
+ for (const col of Array.isArray(d?.columns) ? d.columns : []) {
745
+ if (!col || col.id == null) continue;
746
+ const cid = String(col.id);
747
+ if (slots[cid]) continue;
748
+ slots[cid] = [];
749
+ cols.push({ id: cid, col });
750
+ for (const card of Array.isArray(col.cards) ? col.cards : []) {
751
+ if (!card || card.id == null) continue;
752
+ const id = String(card.id);
753
+ if (cardById.has(id)) continue;
754
+ cardById.set(id, card);
755
+ slots[cid].push(id);
756
+ want[id] = cid;
757
+ }
758
+ }
759
+ const changes = [];
760
+ const nextStatus = /* @__PURE__ */ new Map();
761
+ for (const [id, card] of cardById) {
762
+ const s = statusOf(card);
763
+ const prev = this.#lastStatus.get(id);
764
+ if (prev && prev !== s) changes.push(card);
765
+ nextStatus.set(id, s);
766
+ }
767
+ this.#lastStatus = nextStatus;
768
+ const drawn = /* @__PURE__ */ Object.create(null);
769
+ for (const [id, sh] of this.#cards) if (want[id] && slots[sh.col]) drawn[id] = sh.col;
770
+ const held = holdLanes(want, drawn, this.#holds, now, live && !reduced);
771
+ const placed = placeHolds(slots, held.lanes, this.#order);
772
+ for (const id of placed.dropped) delete held.holds[id];
773
+ this.#holds = held.holds;
774
+ clearTimeout(this.#holdTimer);
775
+ this.#holdTimer = void 0;
776
+ if (held.wakeAt)
777
+ this.#holdTimer = setTimeout(
778
+ () => {
779
+ this.#holdTimer = void 0;
780
+ this.requestRender();
781
+ },
782
+ Math.max(0, held.wakeAt - now) + 20
783
+ );
784
+ const first = /* @__PURE__ */ new Map();
785
+ const firstCol = /* @__PURE__ */ new Map();
786
+ const firstStatus = /* @__PURE__ */ new Map();
787
+ for (const [id, sh] of this.#cards) {
788
+ firstCol.set(id, sh.col);
789
+ firstStatus.set(id, sh.status);
790
+ if (canAnimate) first.set(id, this.#boxOf(sh.el));
791
+ }
792
+ board.style.setProperty("--ib-cols", String(Math.max(1, cols.length)));
793
+ cols.forEach(({ id }, i) => {
794
+ let c = this.#cols.get(id);
795
+ if (!c) {
796
+ c = this.#colShell(id);
797
+ this.#cols.set(id, c);
798
+ }
799
+ if (board.children[i] !== c.el) board.insertBefore(c.el, board.children[i] ?? null);
800
+ });
801
+ const fresh = /* @__PURE__ */ new Set();
802
+ const order = /* @__PURE__ */ Object.create(null);
803
+ for (const { id: colId, col } of cols) {
804
+ const c = this.#cols.get(colId);
805
+ const ids = placed.slots[colId];
806
+ order[colId] = ids;
807
+ ids.forEach((id, i) => {
808
+ let sh = this.#cards.get(id);
809
+ if (!sh) {
810
+ sh = this.#shell(id);
811
+ this.#cards.set(id, sh);
812
+ fresh.add(id);
813
+ if (startOpen.has(id)) this.#open.add(id);
814
+ }
815
+ this.#patch(sh, id, cardById.get(id), t, openLabel, live && !reduced);
816
+ sh.col = colId;
817
+ if (c.list.children[i] !== sh.el) c.list.insertBefore(sh.el, c.list.children[i] ?? null);
818
+ });
819
+ const name = String(col.label ?? "");
820
+ if (c.name.textContent !== name) c.name.textContent = name;
821
+ const count = String(ids.length);
822
+ if (c.count.textContent !== count) c.count.textContent = count;
823
+ const empty = typeof col.emptyText === "string" && col.emptyText ? col.emptyText : t.empty;
824
+ if (c.empty.textContent !== empty) c.empty.textContent = empty;
825
+ if (c.empty.hidden !== ids.length > 0) c.empty.hidden = ids.length > 0;
826
+ }
827
+ this.#order = order;
828
+ const motion = canAnimate ? this.#motion() : null;
829
+ for (const [id, sh] of this.#cards) {
830
+ if (cardById.has(id)) continue;
831
+ this.#cards.delete(id);
832
+ this.#open.delete(id);
833
+ this.#shown.delete(`${id}\0tok`);
834
+ this.#shown.delete(`${id}\0usd`);
835
+ this.#exit(sh, first.get(id), slots[firstCol.get(id) ?? ""] ? motion : null);
836
+ }
837
+ for (const [id, c] of this.#cols) {
838
+ if (slots[id]) continue;
839
+ c.el.remove();
840
+ this.#cols.delete(id);
841
+ }
842
+ if (motion) {
843
+ for (const [id, sh] of this.#cards) {
844
+ if (fresh.has(id)) {
845
+ sh.el.animate(
846
+ reduced ? [{ opacity: 0 }, { opacity: 1 }] : [
847
+ { opacity: 0, transform: "scale(0.97)" },
848
+ { opacity: 1, transform: "scale(1)" }
849
+ ],
850
+ { duration: motion.fade, easing: motion.easeOut }
851
+ );
852
+ continue;
853
+ }
854
+ const prev = firstStatus.get(id);
855
+ if (!reduced && prev && prev !== sh.status)
856
+ sh.face.querySelector(".ib-tag-icon")?.animate(
857
+ [
858
+ { opacity: 0, transform: "scale(0.6)" },
859
+ { opacity: 1, transform: "scale(1)" }
860
+ ],
861
+ { duration: motion.move, easing: motion.easeOut }
862
+ );
863
+ const a = first.get(id);
864
+ if (!a) continue;
865
+ const wasFlying = sh.el.classList.contains("is-flying");
866
+ if (sh.flip) {
867
+ sh.flip.cancel();
868
+ sh.flip = null;
869
+ }
870
+ if (wasFlying) sh.el.classList.remove("is-flying");
871
+ const b = this.#boxOf(sh.el);
872
+ const dx = a.x - b.x;
873
+ const dy = a.y - b.y;
874
+ if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;
875
+ this.#flip(sh, dx, dy, wasFlying || firstCol.get(id) !== sh.col, motion, reduced);
876
+ }
877
+ }
878
+ if (d)
879
+ this.#announce(
880
+ cols.map((c) => c.col),
881
+ changes,
882
+ t
883
+ );
884
+ if (activeKey && this.root.activeElement?.dataset?.key !== activeKey) {
885
+ const next = this.$$("[data-key]").find((el) => el.dataset.key === activeKey);
886
+ next?.focus({ preventScroll: true });
887
+ }
888
+ this.#tickIn(this.content);
889
+ this.#syncTick();
890
+ if (d) this.#painted = true;
891
+ }
892
+ #patch(sh, id, card, t, openLabel, countUp) {
893
+ const status = statusOf(card);
894
+ if (sh.status !== status) {
895
+ sh.status = status;
896
+ sh.el.setAttribute("data-status", status);
897
+ }
898
+ const face = renderFace(card, t);
899
+ if (sh.faceHtml !== face) {
900
+ sh.face.innerHTML = face;
901
+ sh.faceHtml = face;
902
+ const phase = `-${Math.round((typeof performance !== "undefined" ? performance.now() : Date.now()) % 900)}ms`;
903
+ for (const s of Array.from(sh.face.querySelectorAll(".ib-spin"))) s.style.animationDelay = phase;
904
+ this.#countUp(id, sh.face, countUp);
905
+ }
906
+ const body = renderBody(card, t, openLabel);
907
+ if (sh.bodyHtml !== body) {
908
+ sh.body.innerHTML = body;
909
+ sh.bodyHtml = body;
910
+ }
911
+ if (body) {
912
+ if (sh.face.getAttribute("role") !== "button") {
913
+ sh.face.setAttribute("role", "button");
914
+ sh.face.setAttribute("tabindex", "0");
915
+ sh.face.setAttribute("aria-controls", sh.expand.id);
916
+ }
917
+ } else if (sh.face.hasAttribute("role")) {
918
+ for (const a of ["role", "tabindex", "aria-controls", "aria-expanded"]) sh.face.removeAttribute(a);
919
+ this.#open.delete(id);
920
+ }
921
+ this.#applyOpen(sh, this.#open.has(id));
922
+ }
923
+ /** Move = translate only, raised while it changes column. Reduced motion: no travel, a fade where it landed. */
924
+ #flip(sh, dx, dy, raise, m, reduced) {
925
+ if (reduced) {
926
+ if (raise) sh.el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: m.fade, easing: "linear" });
927
+ return;
928
+ }
929
+ if (raise) sh.el.classList.add("is-flying");
930
+ const anim = sh.el.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "translate(0, 0)" }], {
931
+ duration: m.move,
932
+ easing: m.easeInOut,
933
+ composite: "add"
934
+ });
935
+ sh.flip = anim;
936
+ anim.onfinish = () => {
937
+ if (sh.flip !== anim) return;
938
+ sh.flip = null;
939
+ sh.el.classList.remove("is-flying");
940
+ };
941
+ }
942
+ /** Exit = opacity only, absolutely placed in its column where it was drawn, so the rest FLIP into the gap. */
943
+ #exit(sh, box, m) {
944
+ if (sh.flip) {
945
+ sh.flip.cancel();
946
+ sh.flip = null;
947
+ }
948
+ const section = sh.el.closest(".ib-col");
949
+ if (!box || !section || !m) {
950
+ sh.el.remove();
951
+ return;
952
+ }
953
+ const hr = section.getBoundingClientRect();
954
+ const width = sh.el.offsetWidth;
955
+ sh.el.removeAttribute("data-card");
956
+ sh.el.setAttribute("aria-hidden", "true");
957
+ sh.el.setAttribute("inert", "");
958
+ sh.el.classList.add("is-exiting");
959
+ sh.el.style.left = `${box.x - hr.left}px`;
960
+ sh.el.style.top = `${box.y - hr.top}px`;
961
+ sh.el.style.width = `${width}px`;
962
+ section.append(sh.el);
963
+ const anim = sh.el.animate([{ opacity: 1 }, { opacity: 0 }], {
964
+ duration: m.fade,
965
+ easing: m.easeOut,
966
+ fill: "forwards"
967
+ });
968
+ anim.onfinish = anim.oncancel = () => sh.el.remove();
969
+ }
970
+ /**
971
+ * The polite region says "N need you, M running" — the first sentence at once, then only real changes; a burst
972
+ * within ANNOUNCE_MS is said once (the last text). One card changing status is named first.
973
+ */
974
+ #announce(columns, changes, t) {
975
+ const live = this.#live;
976
+ if (!live) return;
977
+ const c = boardCounts(columns);
978
+ const counts = t.announce(c.needs, c.running);
979
+ if (!changes.length && counts === this.#lastCounts) return;
980
+ this.#lastCounts = counts;
981
+ const text = changes.length === 1 ? `${t.changed(String(changes[0].title ?? ""), statusWord(changes[0], t))} ${counts}` : counts;
982
+ this.#announcePending = text;
983
+ if (!live.textContent && !this.#announceTimer) {
984
+ live.textContent = text;
985
+ return;
986
+ }
987
+ if (this.#announceTimer || live.textContent === text) return;
988
+ this.#announceTimer = setTimeout(() => {
989
+ this.#announceTimer = void 0;
990
+ if (live.textContent !== this.#announcePending) live.textContent = this.#announcePending;
991
+ }, ANNOUNCE_MS);
992
+ }
993
+ /** Clocks and ages are written in place (never by a re-render), once a second while any is on the board. */
994
+ #tickIn(root) {
995
+ const now = Date.now();
996
+ const t = this.#t;
997
+ for (const el of Array.from(root.querySelectorAll("[data-clock]"))) {
998
+ const from = Number(el.dataset.from) || 0;
999
+ const acc = Number(el.dataset.acc) || 0;
1000
+ const text = fmtDuration(acc + (from ? Math.max(0, now - from) : 0));
1001
+ if (el.textContent !== text) el.textContent = text;
1002
+ }
1003
+ for (const el of Array.from(root.querySelectorAll("[data-at]"))) {
1004
+ const text = t.ago(Number(el.dataset.at) || 0, now);
1005
+ if (el.textContent !== text) el.textContent = text;
1006
+ }
1007
+ }
1008
+ #syncTick() {
1009
+ const need = !!this.content.querySelector("[data-clock], [data-at]");
1010
+ if (need && !this.#tickTimer) this.#tickTimer = setInterval(() => this.#tickIn(this.content), 1e3);
1011
+ else if (!need && this.#tickTimer) {
1012
+ clearInterval(this.#tickTimer);
1013
+ this.#tickTimer = void 0;
1014
+ }
1015
+ }
1016
+ /** Tokens and cost count up (900 ms, ease-out cubic) when they grow on a card on screen; else they are set. */
1017
+ #countUp(id, root, animate) {
1018
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
1019
+ for (const el of Array.from(root.querySelectorAll("[data-cnt]"))) {
1020
+ const kind = el.dataset.cnt ?? "tok";
1021
+ const key = `${id}\0${kind}`;
1022
+ const to = Number(el.dataset.to) || 0;
1023
+ const shown = this.#shown.get(key);
1024
+ if (shown === void 0 || !(to > shown) || !animate || typeof requestAnimationFrame !== "function") {
1025
+ this.#shown.set(key, to);
1026
+ this.#counting.delete(key);
1027
+ continue;
1028
+ }
1029
+ el.textContent = fmtCount(kind, shown);
1030
+ this.#counting.set(key, { el, kind, from: shown, to, t0: now });
1031
+ }
1032
+ if (this.#counting.size && !this.#raf && typeof requestAnimationFrame === "function")
1033
+ this.#raf = requestAnimationFrame(this.#step);
1034
+ }
1035
+ #step = (now) => {
1036
+ for (const [key, c] of this.#counting) {
1037
+ const p = Math.min(1, Math.max(0, (now - c.t0) / COUNT_MS));
1038
+ const v = p >= 1 ? c.to : c.from + (c.to - c.from) * (1 - (1 - p) ** 3);
1039
+ const text = fmtCount(c.kind, v);
1040
+ if (c.el.textContent !== text) c.el.textContent = text;
1041
+ this.#shown.set(key, v);
1042
+ if (p >= 1) this.#counting.delete(key);
1043
+ }
1044
+ this.#raf = this.#counting.size ? requestAnimationFrame(this.#step) : 0;
1045
+ };
1046
+ }
1047
+ define("ui-agent-inbox", UiAgentInbox);
1048
+ export {
1049
+ ANNOUNCE_MS,
1050
+ BOARD_HOLD_MS,
1051
+ UiAgentInbox,
1052
+ agentDots,
1053
+ artifactKind,
1054
+ boardCounts,
1055
+ columnsByStatus,
1056
+ holdLanes,
1057
+ placeHolds,
1058
+ statusOf,
1059
+ statusTone
1060
+ };
1061
+ //# sourceMappingURL=agent-inbox.js.map