@reticlehq/browser 2.8.0 → 2.9.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 (79) hide show
  1. package/NOTICE +7 -0
  2. package/dist/actions/actions-dom.js +23 -15
  3. package/dist/actions/actions.d.ts +17 -0
  4. package/dist/actions/actions.js +132 -29
  5. package/dist/actions/synthetic-input.d.ts +9 -0
  6. package/dist/actions/synthetic-input.js +40 -0
  7. package/dist/commands/commands.js +11 -1
  8. package/dist/connect-options.d.ts +9 -9
  9. package/dist/dom/a11y.d.ts +9 -0
  10. package/dist/dom/a11y.js +19 -0
  11. package/dist/dom/dom-ignore.d.ts +19 -6
  12. package/dist/dom/dom-ignore.js +20 -13
  13. package/dist/dom/query.d.ts +2 -2
  14. package/dist/dom/query.js +85 -45
  15. package/dist/dom/snapshot.d.ts +5 -0
  16. package/dist/dom/snapshot.js +34 -0
  17. package/dist/dom/source.d.ts +14 -0
  18. package/dist/dom/source.js +16 -0
  19. package/dist/observers/install-all.js +4 -1
  20. package/dist/observers/ipc.d.ts +16 -0
  21. package/dist/observers/ipc.js +19 -1
  22. package/dist/observers/network.d.ts +8 -0
  23. package/dist/observers/network.js +24 -3
  24. package/dist/observers/state.js +21 -2
  25. package/dist/presenter/presenter-brand.d.ts +24 -7
  26. package/dist/presenter/presenter-brand.js +23 -6
  27. package/dist/presenter/presenter-config.d.ts +102 -9
  28. package/dist/presenter/presenter-config.js +94 -8
  29. package/dist/presenter/presenter-controls.d.ts +10 -10
  30. package/dist/presenter/presenter-controls.js +175 -85
  31. package/dist/presenter/presenter-dock-layout.d.ts +6 -0
  32. package/dist/presenter/presenter-dock-layout.js +170 -0
  33. package/dist/presenter/presenter-drag.d.ts +48 -0
  34. package/dist/presenter/presenter-drag.js +198 -0
  35. package/dist/presenter/presenter-effects.d.ts +1 -1
  36. package/dist/presenter/presenter-effects.js +1 -1
  37. package/dist/presenter/presenter-glow.d.ts +5 -5
  38. package/dist/presenter/presenter-glow.js +7 -7
  39. package/dist/presenter/presenter-heroicons-data.d.ts +41 -0
  40. package/dist/presenter/presenter-heroicons-data.js +38 -0
  41. package/dist/presenter/presenter-hud-chrome.d.ts +17 -0
  42. package/dist/presenter/presenter-hud-chrome.js +41 -0
  43. package/dist/presenter/presenter-icons.d.ts +44 -0
  44. package/dist/presenter/presenter-icons.js +100 -0
  45. package/dist/presenter/presenter-log.d.ts +27 -5
  46. package/dist/presenter/presenter-log.js +114 -28
  47. package/dist/presenter/presenter-report-copy.d.ts +83 -0
  48. package/dist/presenter/presenter-report-copy.js +127 -0
  49. package/dist/presenter/presenter-report-styles.d.ts +7 -0
  50. package/dist/presenter/presenter-report-styles.js +102 -0
  51. package/dist/presenter/presenter-report.d.ts +23 -0
  52. package/dist/presenter/presenter-report.js +217 -0
  53. package/dist/presenter/presenter-run-state.d.ts +1 -1
  54. package/dist/presenter/presenter-settings-styles.d.ts +2 -0
  55. package/dist/presenter/presenter-settings-styles.js +153 -0
  56. package/dist/presenter/presenter-settings.d.ts +82 -0
  57. package/dist/presenter/presenter-settings.js +494 -0
  58. package/dist/presenter/presenter-shell-styles.d.ts +1 -0
  59. package/dist/presenter/presenter-shell-styles.js +375 -0
  60. package/dist/presenter/presenter-shell.d.ts +48 -0
  61. package/dist/presenter/presenter-shell.js +408 -0
  62. package/dist/presenter/presenter-styles.d.ts +9 -4
  63. package/dist/presenter/presenter-styles.js +61 -125
  64. package/dist/presenter/presenter-tally.d.ts +8 -11
  65. package/dist/presenter/presenter-tally.js +48 -23
  66. package/dist/presenter/presenter-test-helpers.d.ts +1 -1
  67. package/dist/presenter/presenter-test-helpers.js +1 -1
  68. package/dist/presenter/presenter-verbs.js +2 -2
  69. package/dist/presenter/presenter-workspace.d.ts +15 -0
  70. package/dist/presenter/presenter-workspace.js +180 -0
  71. package/dist/presenter/presenter.d.ts +15 -10
  72. package/dist/presenter/presenter.js +194 -46
  73. package/dist/reticle.d.ts +2 -2
  74. package/dist/reticle.js +23 -19
  75. package/dist/review/annotator-styles.d.ts +6 -0
  76. package/dist/review/annotator-styles.js +81 -0
  77. package/dist/review/annotator.d.ts +29 -17
  78. package/dist/review/annotator.js +477 -146
  79. package/package.json +7 -4
@@ -0,0 +1,408 @@
1
+ import { PresenterReport, reportPanelHtml } from './presenter-report.js';
2
+ import { CHAT_MIN_ATTR, CHAT_PILL_ATTR, REPORT_ATTR, REPORT_BTN_ATTR, ANNOTATE_BTN_ATTR, CHAT_ATTR, CHAT_TOGGLE_ATTR, CHAT_PANEL_ATTR, DOCK_ATTR, FAB_ATTR, MIN_ATTR, HUD_DRAGGED_ATTR, SETTINGS_ATTR, SETTINGS_BTN_ATTR, } from './presenter-config.js';
3
+ import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './presenter-brand.js';
4
+ import { settleLogAtLatest } from './presenter-log.js';
5
+ import { installHudDragHandles, installHudPositionGuards } from './presenter-drag.js';
6
+ import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
7
+ import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './presenter-icons.js';
8
+ import { HUD_SURFACE_CLASS, HUD_LOG_WELL_CLASS } from './presenter-hud-chrome.js';
9
+ import { CONTROLS_TOOLBAR_HTML } from './presenter-controls.js';
10
+ import { PresenterSettingsPanel, settingsPanelHtml, } from './presenter-settings.js';
11
+ const DRAG_HANDLE_CLASS = 'reticle-toolbar-drag';
12
+ const TRANSITION_LOCK_MS = 120;
13
+ const ANNOTATE_LABEL = 'Annotate';
14
+ const CHAT_MIN_LABEL = 'Minimise chat';
15
+ /** The minimised chat capsule's label - it reopens the panel. */
16
+ const CHAT_PILL_LABEL = 'Open agent chat';
17
+ /** The toolbar entry to the impact report. */
18
+ const REPORT_LABEL = 'Impact';
19
+ const SETTINGS_LABEL = 'Settings';
20
+ const EXIT_LABEL = 'Exit';
21
+ /**
22
+ * Morphing HUD shell: circular FAB ↔ icon toolbar, plus the agent chat panel above.
23
+ * Expand enters annotation mode; the page stays clickable except for captured annotate clicks.
24
+ */
25
+ export class HudShell {
26
+ #root;
27
+ #dock;
28
+ #fab;
29
+ #chatPanel;
30
+ #chatToggle;
31
+ #collapseBtn;
32
+ #settings;
33
+ #dragTeardown;
34
+ #layoutTeardown;
35
+ #transitionLock = false;
36
+ #suppressFabClick = false;
37
+ #annotateBtn;
38
+ /**
39
+ * Whether the USER wants to annotate. Distinct from whether annotation is currently possible,
40
+ * which also needs a live session and an expanded HUD — this is the half the user controls, and
41
+ * conflating the two is why there was no way to keep the HUD open without annotating.
42
+ *
43
+ * OFF until asked for. It began as `true` to preserve the old behaviour where expanding the HUD
44
+ * silently entered annotate mode, and that is exactly what made the toolbar icon look permanently
45
+ * lit: the toolbar is only visible while expanded, so an intent that defaults to on is on every
46
+ * time you can see it. Annotate mode also captures clicks, so defaulting it on means a click lands
47
+ * as a mark before anyone asked for one. It is a mode now, and modes are entered deliberately.
48
+ */
49
+ #annotateOn = false;
50
+ #toggleSync;
51
+ #report = new PresenterReport({
52
+ onBeforeOpen: () => {
53
+ // One panel at a time in the slot above the toolbar. The chat, the settings and the report
54
+ // all anchor there, so opening one over another stacks two glass cards and reads as a bug.
55
+ this.#settings.close();
56
+ this.closeChat();
57
+ },
58
+ });
59
+ #callbacks;
60
+ constructor(callbacks = {}) {
61
+ this.#callbacks = callbacks;
62
+ this.#settings = new PresenterSettingsPanel({
63
+ ...callbacks.settings,
64
+ onBeforeOpen: () => {
65
+ // Settings takes the slot from BOTH neighbours. Closing only the chat left the report
66
+ // sitting behind the settings card - two glass panels stacked in one anchor.
67
+ this.closeChat();
68
+ this.#report.close();
69
+ callbacks.settings?.onBeforeOpen?.();
70
+ },
71
+ });
72
+ }
73
+ /** Markup for the dock wrapper (chat panel + morphing HUD shell). */
74
+ static dockHtml(actStripHtml, bannerHtml, logAttr, flowsHtml, footHtml) {
75
+ const annotate = hiToggleIconHtml(PresenterIcon.ANNOTATE, PRESENTER_ICON_SIZE.TOOLBAR);
76
+ const chart = hiToggleIconHtml(PresenterIcon.CHART, PRESENTER_ICON_SIZE.TOOLBAR);
77
+ const gear = hiToggleIconHtml(PresenterIcon.GEAR, PRESENTER_ICON_SIZE.TOOLBAR);
78
+ const exit = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.TOOLBAR);
79
+ return `<div ${DOCK_ATTR}>
80
+ <div ${CHAT_PANEL_ATTR} class="reticle-chat-panel ${HUD_SURFACE_CLASS}" role="dialog" aria-label="Reticle agent chat" aria-hidden="true">
81
+ <div class="reticle-chat-head">
82
+ <span class="reticle-chat-brand">${MARK_SVG}<span class="reticle-chat-brandname">${BRAND_NAME}</span></span>
83
+ </div>
84
+ <button type="button" ${CHAT_MIN_ATTR} class="reticle-chat-min" title="${CHAT_MIN_LABEL}" aria-label="${CHAT_MIN_LABEL}">${hiIconHtml(PresenterIcon.CARET_DOWN, PRESENTER_ICON_SIZE.TOOLBAR)}</button>
85
+ ${actStripHtml}
86
+ <span class="reticle-tally" data-reticle-tally hidden></span>
87
+ <span class="reticle-chip" data-reticle-chip></span>
88
+ ${bannerHtml}
89
+ <div class="${HUD_LOG_WELL_CLASS}"><div ${logAttr}></div></div>
90
+ ${flowsHtml}
91
+ ${footHtml}
92
+ </div>
93
+ <button type="button" ${CHAT_PILL_ATTR} class="reticle-chat-pill" title="${CHAT_PILL_LABEL}" aria-label="${CHAT_PILL_LABEL}">
94
+ ${MARK_SVG}
95
+ <span class="reticle-chat-pill-text" data-reticle-chat-pill-text></span>
96
+ <span class="reticle-chat-pill-time" data-reticle-chat-pill-time></span>
97
+ <span class="reticle-chat-pill-caret" aria-hidden="true">${hiIconHtml(PresenterIcon.CARET_DOWN, PRESENTER_ICON_SIZE.TOOLBAR)}</span>
98
+ </button>
99
+ ${settingsPanelHtml()}
100
+ ${reportPanelHtml()}
101
+ <div data-reticle-hud>
102
+ <div class="reticle-hud-deco" aria-hidden="true"></div>
103
+ ${FAB_TOGGLE_HTML}
104
+ <div class="reticle-toolbar ${DRAG_HANDLE_CLASS}" role="toolbar" aria-label="Reticle controls">
105
+ <div class="reticle-toolbar-actions">${CONTROLS_TOOLBAR_HTML}</div>
106
+ <span class="reticle-tb-sep" aria-hidden="true"></span>
107
+ <div class="reticle-toolbar-chrome">
108
+ <div class="reticle-tb-wrap">
109
+ <button type="button" ${ANNOTATE_BTN_ATTR} class="reticle-tb-btn reticle-tb-btn--toggle" title="${ANNOTATE_LABEL}" aria-label="${ANNOTATE_LABEL}" aria-pressed="false" data-active="0">${annotate}</button>
110
+ <span class="reticle-tb-tip">${ANNOTATE_LABEL}</span>
111
+ </div>
112
+ <div class="reticle-tb-wrap">
113
+ <button type="button" ${REPORT_BTN_ATTR} class="reticle-tb-btn reticle-tb-btn--toggle" title="${REPORT_LABEL}" aria-label="${REPORT_LABEL}" aria-pressed="false" data-active="0">${chart}</button>
114
+ <span class="reticle-tb-tip">${REPORT_LABEL}</span>
115
+ </div>
116
+ <div class="reticle-tb-wrap">
117
+ <button type="button" ${SETTINGS_BTN_ATTR} class="reticle-tb-btn reticle-tb-btn--toggle" title="${SETTINGS_LABEL}" aria-label="${SETTINGS_LABEL}" aria-pressed="false" data-active="0">${gear}</button>
118
+ <span class="reticle-tb-tip">${SETTINGS_LABEL}</span>
119
+ </div>
120
+ <div class="reticle-tb-wrap">
121
+ <button type="button" data-reticle-min-btn class="reticle-tb-btn" title="${EXIT_LABEL}" aria-label="${EXIT_LABEL}">${exit}</button>
122
+ <span class="reticle-tb-tip">${EXIT_LABEL}<span class="reticle-tb-kbd">Esc</span></span>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>`;
128
+ }
129
+ /** The impact report, so the presenter can feed it and the chat can open it. */
130
+ get report() {
131
+ return this.#report;
132
+ }
133
+ /** Light exactly the toolbar buttons whose panels are open. */
134
+ #syncToolbarToggles() {
135
+ const root = this.#root;
136
+ if (root === undefined)
137
+ return;
138
+ const lit = (btnAttr, on) => {
139
+ const btn = root.querySelector(`[${btnAttr}]`);
140
+ btn?.setAttribute('data-active', on ? '1' : '0');
141
+ btn?.setAttribute('aria-pressed', on ? 'true' : 'false');
142
+ };
143
+ lit(CHAT_TOGGLE_ATTR, '1' === root.getAttribute(CHAT_ATTR));
144
+ lit(SETTINGS_BTN_ATTR, '1' === root.getAttribute(SETTINGS_ATTR));
145
+ lit(REPORT_BTN_ATTR, '1' === root.getAttribute(REPORT_ATTR));
146
+ }
147
+ /** Does the user currently want to annotate? */
148
+ isAnnotateOn() {
149
+ return this.#annotateOn;
150
+ }
151
+ /** Set the toggle and reflect it on the button, without firing the callback. */
152
+ setAnnotateOn(on) {
153
+ this.#annotateOn = on;
154
+ this.#annotateBtn?.setAttribute('aria-pressed', on ? 'true' : 'false');
155
+ this.#annotateBtn?.setAttribute('data-active', on ? '1' : '0');
156
+ }
157
+ mount(root) {
158
+ this.#root = root;
159
+ this.#dock = root.querySelector(`[${DOCK_ATTR}]`) ?? undefined;
160
+ const fabEl = root.querySelector(`[${FAB_ATTR}]`);
161
+ this.#fab = fabEl instanceof HTMLButtonElement ? fabEl : undefined;
162
+ const chatPanelEl = root.querySelector(`[${CHAT_PANEL_ATTR}]`);
163
+ this.#chatPanel = chatPanelEl instanceof HTMLElement ? chatPanelEl : undefined;
164
+ const chatToggleEl = root.querySelector(`[${CHAT_TOGGLE_ATTR}]`);
165
+ this.#chatToggle = chatToggleEl instanceof HTMLElement ? chatToggleEl : undefined;
166
+ const annotateEl = root.querySelector(`[${ANNOTATE_BTN_ATTR}]`);
167
+ this.#annotateBtn = annotateEl instanceof HTMLButtonElement ? annotateEl : undefined;
168
+ this.#annotateBtn?.addEventListener('click', (e) => {
169
+ e.preventDefault();
170
+ e.stopPropagation();
171
+ this.setAnnotateOn(!this.#annotateOn);
172
+ this.#callbacks.onAnnotateToggle?.(this.#annotateOn);
173
+ });
174
+ const pillEl = root.querySelector(`[${CHAT_PILL_ATTR}]`);
175
+ pillEl?.addEventListener('click', (e) => {
176
+ e.preventDefault();
177
+ e.stopPropagation();
178
+ this.showChat();
179
+ });
180
+ const chatMinEl = root.querySelector(`[${CHAT_MIN_ATTR}]`);
181
+ chatMinEl?.addEventListener('click', (e) => {
182
+ e.preventDefault();
183
+ e.stopPropagation();
184
+ this.closeChat();
185
+ });
186
+ const collapseEl = root.querySelector('[data-reticle-min-btn]');
187
+ this.#collapseBtn = collapseEl instanceof HTMLButtonElement ? collapseEl : undefined;
188
+ root.setAttribute(MIN_ATTR, '1');
189
+ root.setAttribute(SETTINGS_ATTR, '0');
190
+ root.removeAttribute(CHAT_ATTR);
191
+ this.#settings.mount(root);
192
+ this.#report.mount(root);
193
+ // The toolbar's lit state FOLLOWS the panels, rather than being set by whoever was clicked.
194
+ // Set at click time, a button stayed lit after its panel was closed by the panel that replaced
195
+ // it - two icons active, one panel open. The observer is the only place that can be right for
196
+ // every path: the toolbar, Escape, click-outside, and one panel closing another.
197
+ this.#toggleSync = new MutationObserver(() => this.#syncToolbarToggles());
198
+ this.#toggleSync.observe(root, {
199
+ attributes: true,
200
+ attributeFilter: [CHAT_ATTR, SETTINGS_ATTR, REPORT_ATTR],
201
+ });
202
+ this.#syncToolbarToggles();
203
+ root.querySelector(`[${REPORT_BTN_ATTR}]`)?.addEventListener('click', (e) => {
204
+ e.preventDefault();
205
+ e.stopPropagation();
206
+ this.#report.toggle();
207
+ });
208
+ this.#fab?.addEventListener('click', (e) => {
209
+ e.stopPropagation();
210
+ if (this.#suppressFabClick) {
211
+ this.#suppressFabClick = false;
212
+ return;
213
+ }
214
+ this.expand();
215
+ });
216
+ this.#collapseBtn?.addEventListener('click', (e) => {
217
+ e.stopPropagation();
218
+ this.collapse();
219
+ });
220
+ this.#chatToggle?.addEventListener('click', (e) => {
221
+ e.stopPropagation();
222
+ this.toggleChat();
223
+ });
224
+ const toolbarDrag = root.querySelector(`.${DRAG_HANDLE_CLASS}`);
225
+ const dragHandles = [this.#fab, toolbarDrag].filter((el) => el instanceof HTMLElement);
226
+ if (this.#dock !== undefined && dragHandles.length > 0) {
227
+ this.#dragTeardown = installHudDragHandles(this.#dock, dragHandles, {
228
+ onDragMove: () => {
229
+ this.#suppressFabClick = true;
230
+ },
231
+ onDragEnd: (moved) => {
232
+ if (moved && this.isCollapsed())
233
+ this.#suppressFabClick = true;
234
+ },
235
+ });
236
+ this.#layoutTeardown = installHudPositionGuards(this.#dock, root);
237
+ }
238
+ document.addEventListener('pointerdown', this.#onDocPointerDown);
239
+ document.addEventListener('keydown', this.#onKeyDown);
240
+ }
241
+ teardown() {
242
+ document.removeEventListener('pointerdown', this.#onDocPointerDown);
243
+ document.removeEventListener('keydown', this.#onKeyDown);
244
+ this.#dragTeardown?.();
245
+ this.#dragTeardown = undefined;
246
+ this.#layoutTeardown?.();
247
+ this.#layoutTeardown = undefined;
248
+ this.#settings.teardown();
249
+ this.#root = undefined;
250
+ this.#dock = undefined;
251
+ this.#fab = undefined;
252
+ this.#chatPanel = undefined;
253
+ this.#chatToggle = undefined;
254
+ this.#collapseBtn = undefined;
255
+ }
256
+ isCollapsed() {
257
+ return '1' === this.#root?.getAttribute(MIN_ATTR);
258
+ }
259
+ isChatOpen() {
260
+ return '1' === this.#root?.getAttribute(CHAT_ATTR);
261
+ }
262
+ expand() {
263
+ if (this.#root === undefined || !this.isCollapsed())
264
+ return;
265
+ if (this.#transitionLock)
266
+ return;
267
+ this.#lockTransition();
268
+ this.#root.setAttribute(MIN_ATTR, '0');
269
+ if (this.#fab !== undefined)
270
+ this.#fab.setAttribute('aria-expanded', 'true');
271
+ this.#callbacks.onExpand?.();
272
+ // The chat IS the HUD's content: expanding to a toolbar with nothing above it made the agent's
273
+ // log something you had to know to go looking for. Unconditional on purpose — the way to a bare
274
+ // toolbar is the chat's own minimise button, not a preference. `Auto-open chat` is a different
275
+ // question (should it appear with no click at all, at session start) and stays separate, because
276
+ // wiring expand to it made session start expand the HUD and the FAB never appeared.
277
+ // `openChat` re-enters `expand` only when collapsed, and MIN_ATTR is already cleared above.
278
+ this.openChat();
279
+ if (this.#dock !== undefined)
280
+ scheduleSyncDockLayout(this.#dock, this.#root);
281
+ }
282
+ collapse() {
283
+ if (this.#root === undefined)
284
+ return;
285
+ this.#transitionLock = false;
286
+ this.closeChat();
287
+ this.#settings.close();
288
+ this.#root.setAttribute(MIN_ATTR, '1');
289
+ if (this.#fab !== undefined)
290
+ this.#fab.setAttribute('aria-expanded', 'false');
291
+ this.#callbacks.onCollapse?.();
292
+ }
293
+ openChat() {
294
+ if (this.#root === undefined)
295
+ return;
296
+ if (this.isCollapsed())
297
+ this.expand();
298
+ this.#settings.close();
299
+ if (this.isChatOpen())
300
+ return;
301
+ this.#root.setAttribute(CHAT_ATTR, '1');
302
+ // The panel had no layout while it was closed, so its feed could not follow the newest row.
303
+ // Now that it does, put it there before it is looked at.
304
+ const log = this.#root.querySelector('[data-reticle-log]');
305
+ if (log !== null)
306
+ settleLogAtLatest(log);
307
+ this.#chatPanel?.setAttribute('aria-hidden', 'false');
308
+ this.#chatToggle?.setAttribute('data-active', '1');
309
+ this.#chatToggle?.setAttribute('aria-pressed', 'true');
310
+ this.#callbacks.onChatOpen?.();
311
+ if (this.#dock !== undefined)
312
+ scheduleSyncDockLayout(this.#dock, this.#root);
313
+ const input = this.#root.querySelector('[data-reticle-input]');
314
+ if (input !== null && !input.disabled) {
315
+ requestAnimationFrame(() => input.focus());
316
+ }
317
+ }
318
+ closeChat() {
319
+ if (this.#root === undefined || !this.isChatOpen())
320
+ return;
321
+ this.#root.removeAttribute(CHAT_ATTR);
322
+ this.#chatPanel?.setAttribute('aria-hidden', 'true');
323
+ this.#chatToggle?.setAttribute('data-active', '0');
324
+ this.#chatToggle?.setAttribute('aria-pressed', 'false');
325
+ this.#callbacks.onChatClose?.();
326
+ if (this.#dock !== undefined)
327
+ scheduleSyncDockLayout(this.#dock, this.#root);
328
+ }
329
+ toggleChat() {
330
+ if (this.isChatOpen())
331
+ this.closeChat();
332
+ else
333
+ this.showChat();
334
+ }
335
+ /**
336
+ * The chat, asked for by a PERSON - so it takes the slot from the report.
337
+ *
338
+ * `openChat` is also called by `expand`, which the agent triggers at session start; routing that
339
+ * through here closed the impact report every time the agent touched the app, which is precisely
340
+ * when you are reading it.
341
+ */
342
+ showChat() {
343
+ this.#report.close();
344
+ this.openChat();
345
+ }
346
+ /** Pulse the FAB when new activity arrives while collapsed. */
347
+ pulseFab(active) {
348
+ this.#fab?.setAttribute('data-pulse', active ? '1' : '0');
349
+ }
350
+ #lockTransition() {
351
+ this.#transitionLock = true;
352
+ window.setTimeout(() => {
353
+ this.#transitionLock = false;
354
+ }, TRANSITION_LOCK_MS);
355
+ }
356
+ /**
357
+ * A click on the page dismisses the SETTINGS popover - and nothing else.
358
+ *
359
+ * The chat used to close here too, which was wrong from three directions: Reticle's own clicks
360
+ * land on the page (synthetic in-page, or a genuine OS event when it drives through CDP), a click
361
+ * in annotate mode is placing a mark, and a person clicking around their app while watching the
362
+ * log is not asking for the log to go away. The chat has three deliberate ways out - its minimise
363
+ * button, the toolbar toggle, and Escape - and that is all it needs.
364
+ */
365
+ #onDocPointerDown = (e) => {
366
+ if (this.#root === undefined || this.#dock === undefined)
367
+ return;
368
+ if (!this.#settings.isOpen())
369
+ return;
370
+ const target = e.target;
371
+ if (!(target instanceof Node))
372
+ return;
373
+ if (this.#dock.contains(target))
374
+ return;
375
+ if (this.#settings.contains(target))
376
+ return;
377
+ this.#settings.close();
378
+ };
379
+ #onKeyDown = (e) => {
380
+ if ('Escape' !== e.key || this.#root === undefined)
381
+ return;
382
+ const target = e.target;
383
+ if (target instanceof HTMLElement &&
384
+ ('INPUT' === target.tagName ||
385
+ 'TEXTAREA' === target.tagName ||
386
+ true === target.isContentEditable)) {
387
+ return;
388
+ }
389
+ if (this.isChatOpen()) {
390
+ e.preventDefault();
391
+ this.closeChat();
392
+ return;
393
+ }
394
+ if (this.#settings.isOpen()) {
395
+ e.preventDefault();
396
+ this.#settings.close();
397
+ return;
398
+ }
399
+ if (!this.isCollapsed()) {
400
+ e.preventDefault();
401
+ this.collapse();
402
+ }
403
+ };
404
+ }
405
+ /** Whether the dock has been dragged off the default bottom-right position. */
406
+ export function isDockDragged(dock) {
407
+ return '1' === dock.getAttribute(HUD_DRAGGED_ATTR);
408
+ }
@@ -1,6 +1,11 @@
1
1
  /**
2
- * All presenter overlay CSS — the glow border, synthetic cursor/ring/ripple, and the glassy HUD.
3
- * Split out of presenter.ts so that file stays a cohesive controller under the size cap; this is
4
- * pure style data (no behavior). LOG_CSS + CONTROLS_CSS are composed in at the end as before.
2
+ * All presenter overlay CSS - glow border, synthetic cursor/ring/ripple, and the floating HUD shell.
3
+ * Split across shell-styles + controls + log modules so each file stays under the size cap.
5
4
  */
6
- export declare const PRESENTER_CSS = "\n[data-reticle-glow]{position:fixed;inset:0;pointer-events:none;z-index:2147483600;opacity:0;\n transition:opacity .25s ease;box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 28px 6px rgba(99,102,241,.45);}\n[data-reticle-glow][data-on=\"1\"]{opacity:1;animation:reticle-pulse 1.6s ease-in-out infinite;}\n[data-reticle-glow][data-on=\"1\"][data-busy=\"1\"]{animation:reticle-shimmer 1.1s ease-in-out infinite;}\n@keyframes reticle-pulse{0%,100%{box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 22px 4px rgba(99,102,241,.35)}\n 50%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 40px 10px rgba(99,102,241,.6)}}\n@keyframes reticle-shimmer{0%,100%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 34px 8px rgba(99,102,241,.55)}\n 50%{box-shadow:inset 0 0 0 3px rgba(140,142,255,1),inset 0 0 48px 12px rgba(99,102,241,.7)}}\n[data-reticle-cursor]{position:fixed;top:0;left:0;width:22px;height:22px;margin:-11px 0 0 -11px;\n border:2px solid #6366f1;border-radius:50%;background:rgba(99,102,241,.25);pointer-events:none;\n z-index:2147483646;opacity:0;transition:transform .32s cubic-bezier(.22,1,.36,1),opacity .2s ease;}\n[data-reticle-cursor][data-on=\"1\"]{opacity:1;}\n[data-reticle-cursor]::after{content:\"\";position:absolute;inset:7px;border-radius:50%;background:#6366f1;}\n[data-reticle-ripple]{position:fixed;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;\n background:rgba(99,102,241,.5);pointer-events:none;z-index:2147483645;animation:reticle-ripple .5s ease-out forwards;}\n@keyframes reticle-ripple{from{transform:scale(.4);opacity:.8}to{transform:scale(5);opacity:0}}\n[data-reticle-ring]{position:fixed;pointer-events:none;z-index:2147483644;border:2px solid #22c55e;border-radius:8px;\n box-shadow:0 0 0 3px rgba(34,197,94,.25);opacity:0;transition:opacity .15s ease;}\n[data-reticle-ring][data-on=\"1\"]{opacity:1;}\n[data-reticle-hud]{\n --reticle-accent:#7c83ff;--reticle-accent-soft:rgba(124,131,255,.16);\n --reticle-bg:rgba(13,15,22,.92);--reticle-bg2:rgba(19,22,32,.88);\n --reticle-fg:#e9ebf2;--reticle-muted:#9aa0b2;--reticle-faint:#6a7186;\n --reticle-line:rgba(255,255,255,.09);--reticle-line2:rgba(255,255,255,.05);\n --reticle-read:#54d2e6;--reticle-ok:#3dd7a6;--reticle-bad:#ff7a7a;\n --reticle-font:\"Inter\",system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;\n position:fixed;left:50%;right:auto;bottom:20px;box-sizing:border-box;\n width:384px;height:468px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);\n display:flex;flex-direction:column;overflow:hidden;text-align:left;z-index:2147483647;pointer-events:none;\n font-family:var(--reticle-font);font-size:13px;line-height:1.5;color:var(--reticle-fg);-webkit-font-smoothing:antialiased;\n background:linear-gradient(180deg,var(--reticle-bg),var(--reticle-bg2));\n /* No backdrop-filter here, deliberately. blur(24px) on this panel was measured as the single most\n expensive thing the whole SDK does: a backdrop blur must re-sample everything behind it whenever\n that content changes, and on a page repainting at 60fps that is continuous full-panel work. On\n the hostile fixture it cost +4.0pp of main thread \u2014 five times the rest of the presenter, and\n more than every observer combined. Removing it took total main-thread time from 2.15s to 1.67s\n over an 8s window. The panel background is ~85% opaque, so the glass effect it bought was\n marginal. If you want it back, add it here and expect to pay that. */\n border:1px solid var(--reticle-line);border-radius:20px;\n box-shadow:0 28px 70px -18px rgba(0,0,0,.66),0 0 0 1px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.07),0 0 54px -22px var(--reticle-accent);\n opacity:0;transform:translateX(-50%) translateY(14px) scale(.985);\n transition:opacity .3s ease,transform .42s cubic-bezier(.16,1,.3,1),height .42s cubic-bezier(.16,1,.3,1),border-radius .42s ease,box-shadow .35s ease;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-hud]{--reticle-accent:#f6b44c;--reticle-accent-soft:rgba(246,180,76,.16);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud]{--reticle-accent:#3dd7a6;--reticle-accent-soft:rgba(61,215,166,.14);}\n[data-reticle-hud]::before{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;\n background:radial-gradient(130% 90% at 50% 0%,var(--reticle-accent-soft),transparent 60%);}\n[data-reticle-hud]>*{position:relative;}\n[data-reticle-hud][data-on=\"1\"]{opacity:1;transform:translateX(-50%) translateY(0) scale(1);}\n/* Click-through: the glassy panel itself never blocks the app \u2014 only its interactive controls\n capture clicks (buttons / inputs), so a human can click straight through the HUD to the page.\n The log auto-scrolls, so it stays click-through too (drag-scroll is traded for click-through). */\n[data-reticle-hud] button,[data-reticle-hud] input,[data-reticle-hud] textarea,\n[data-reticle-hud] select,[data-reticle-hud] [contenteditable]{pointer-events:auto;}\n/* When minimised to a pill, the whole bar is the (single) click target to restore. */\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud][data-on=\"1\"]{pointer-events:auto;}\n[data-reticle-hud] .reticle-hud-head{display:flex;align-items:center;gap:8px;flex:none;\n padding:12px 12px 12px 15px;border-bottom:1px solid var(--reticle-line2);}\n/* Brand block: both marks always in the DOM, CSS picks one off data-reticle-min (see below) so the\n collapsed/expanded swap needs no JS state. Artwork paints with currentColor, so it inherits\n --reticle-fg and reads correctly on any HUD surface instead of baking in the export's #FAFAFA. */\n[data-reticle-hud] .reticle-brand{display:inline-flex;align-items:center;flex:none;color:var(--reticle-fg);}\n[data-reticle-hud] .reticle-mark{display:none;height:18px;width:auto;flex:none;}\n[data-reticle-hud] .reticle-wordmark{display:block;height:16px;width:auto;flex:none;}\n/* Visually hidden, still announced \u2014 the marks are aria-hidden artwork, this names the HUD. */\n[data-reticle-hud] .reticle-brand-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;\n overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;}\n[data-reticle-hud] .reticle-head-sp{flex:1;}\n[data-reticle-hud] .reticle-live{display:none;flex:1;min-width:0;color:var(--reticle-muted);font-size:12.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] .reticle-maxhint{display:none;flex:none;color:var(--reticle-faint);font-size:13px;line-height:1;}\n[data-reticle-hud] .reticle-act-strip{flex:none;padding:7px 15px;border-bottom:1px solid var(--reticle-line2);background:rgba(0,0,0,.14);}\n[data-reticle-hud] .reticle-act{display:block;color:var(--reticle-muted);font-size:11.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] [data-reticle-min-btn]{flex:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;\n width:26px;height:26px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-size:15px;line-height:1;transition:background .15s,color .15s,transform .1s;}\n[data-reticle-hud] [data-reticle-min-btn]:hover{color:var(--reticle-fg);background:rgba(255,255,255,.08);}\n[data-reticle-hud] [data-reticle-min-btn]:active{transform:scale(.94);}\n[data-reticle-hud] .reticle-pass{color:var(--reticle-ok);}[data-reticle-hud] .reticle-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally{flex:none;display:inline-flex;align-items:center;gap:8px;font-size:11.5px;\n font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:.01em;}\n[data-reticle-hud] .reticle-tally[hidden]{display:none;}\n[data-reticle-hud] .reticle-tally .reticle-t-pass{color:var(--reticle-ok);}\n[data-reticle-hud] .reticle-tally .reticle-t-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally [data-z=\"1\"]{opacity:.4;}\n@keyframes reticle-tally-pop{0%{transform:scale(1)}38%{transform:scale(1.3)}100%{transform:scale(1)}}\n[data-reticle-hud] .reticle-tally [data-bump=\"1\"]{display:inline-block;animation:reticle-tally-pop .36s cubic-bezier(.16,1,.3,1);}\n[data-reticle-hud] .reticle-chip{display:none;flex:none;font-size:9px;font-weight:600;letter-spacing:.08em;\n padding:2px 7px;border-radius:6px;vertical-align:middle;}\n[data-reticle-hud] .reticle-chip[data-mode=\"reading\"]{display:inline-block;color:var(--reticle-read);\n background:rgba(84,210,230,.12);border:1px solid rgba(84,210,230,.32);}\n[data-reticle-hud] .reticle-chip[data-mode=\"acting\"]{display:inline-block;color:var(--reticle-accent);\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);}\n[data-reticle-hud] .reticle-chip[data-mode=\"idle\"]{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud]{height:50px;border-radius:25px;cursor:pointer;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-hud-head{border-bottom:none;height:50px;padding:0 12px 0 16px;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-chip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-head-sp,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-min-btn],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-ctl,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-badge,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-act-strip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-log],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-foot],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-banner{display:none;}\n/* Collapsed to a pill: mark only, beside the dot. Expanded: the full wordmark. */\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-wordmark{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-mark{display:block;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-live{display:block;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-maxhint{display:inline-flex;}\n[data-reticle-mode=\"reading\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(34,211,238,.9),inset 0 0 28px 6px rgba(34,211,238,.4);}\n[data-reticle-mode=\"reading\"] [data-reticle-ring]{border-color:#22d3ee;\n box-shadow:0 0 0 3px rgba(34,211,238,.25);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(251,191,36,.9),inset 0 0 28px 6px rgba(251,191,36,.45);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-hud]{--reticle-accent:#fbbf24;--reticle-accent-soft:rgba(251,191,36,.16);}\n/* The two INFINITE animations \u2014 the header dot and the border glow \u2014 are pure decoration and were\n blinking regardless of the user's motion preference. Everything else the presenter animates is a\n one-shot (ripple, tally pop) or a transition, which reduce-motion tolerates. */\n@media (prefers-reduced-motion:reduce){\n [data-reticle-glow][data-on=\"1\"],\n [data-reticle-glow][data-on=\"1\"][data-busy=\"1\"]{animation:none;}}\n\n[data-reticle-log]{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;\n gap:7px;padding:12px 14px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;}\n[data-reticle-log]::-webkit-scrollbar{width:9px;}\n[data-reticle-log]::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-log]::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.26);background-clip:content-box;}\n[data-reticle-log-row]{display:flex;align-items:baseline;gap:8px;font-size:12px;line-height:1.45;\n animation:reticle-row-in .26s cubic-bezier(.16,1,.3,1);}\n@keyframes reticle-row-in{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}\n[data-reticle-log-ts]{flex:none;color:var(--reticle-faint);font-size:10px;font-variant-numeric:tabular-nums;padding-top:1px;}\n[data-reticle-log] .reticle-log-text{flex:1;min-width:0;color:#d6dae4;overflow-wrap:anywhere;word-break:break-word;}\n[data-reticle-log] .reticle-res{flex:none;font-weight:700;}\n[data-reticle-log-row][data-kind=\"human\"]{align-self:flex-end;max-width:88%;\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);border-radius:13px 13px 4px 13px;padding:6px 11px;}\n[data-reticle-log-row][data-kind=\"human\"] [data-reticle-log-ts]{display:none;}\n[data-reticle-log-row][data-kind=\"human\"] .reticle-log-text{color:var(--reticle-fg);}\n\n\n[data-reticle-hud] .reticle-ctl{pointer-events:auto;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;\n height:26px;padding:0 11px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-family:var(--reticle-font);font-size:11px;font-weight:500;letter-spacing:.01em;line-height:1;\n transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-ctl:hover{color:var(--reticle-fg);background:rgba(255,255,255,.09);}\n[data-reticle-hud] .reticle-ctl:active{transform:scale(.95);}\n[data-reticle-hud] .reticle-ctl:disabled{opacity:.35;cursor:default;}\n[data-reticle-hud] [data-reticle-end]{color:#ff9aa2;border-color:rgba(255,107,107,.22);}\n[data-reticle-hud] [data-reticle-end]:hover{color:#ff7a7a;border-color:rgba(255,107,107,.5);background:rgba(255,107,107,.1);}\n[data-reticle-hud] .reticle-badge{display:none;align-items:center;flex:none;font-weight:600;letter-spacing:.1em;font-size:9px;\n color:var(--reticle-accent);border:1px solid var(--reticle-accent);background:var(--reticle-accent-soft);padding:2px 8px;border-radius:999px;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-badge]{display:inline-flex;}\n[data-reticle-hud] [data-reticle-foot]{flex:none;padding:10px 12px 12px;border-top:1px solid var(--reticle-line2);background:rgba(0,0,0,.16);}\n[data-reticle-hud] .reticle-composer{display:flex;align-items:flex-end;gap:6px;background:rgba(255,255,255,.05);\n border:1px solid var(--reticle-line);border-radius:14px;padding:5px 6px 5px 14px;transition:border-color .15s,box-shadow .15s;}\n[data-reticle-hud] .reticle-composer:focus-within{border-color:var(--reticle-accent);box-shadow:0 0 0 3px var(--reticle-accent-soft);}\n[data-reticle-hud] .reticle-msg{flex:1;min-width:0;pointer-events:auto;background:transparent;border:none;outline:none;resize:none;\n box-sizing:border-box;color:var(--reticle-fg);font-family:var(--reticle-font);font-size:13px;line-height:18px;\n height:28px;min-height:28px;max-height:96px;padding:5px 0;overflow-y:auto;\n scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-msg::placeholder{color:var(--reticle-faint);}\n[data-reticle-hud] .reticle-msg:disabled{opacity:.5;}\n[data-reticle-hud] .reticle-send{flex:none;width:30px;height:30px;padding:0;border-radius:10px;border:none;cursor:pointer;pointer-events:auto;\n background:var(--reticle-accent);color:#0b0d14;display:inline-flex;align-items:center;justify-content:center;transition:filter .15s,transform .1s;}\n[data-reticle-hud] .reticle-send svg{display:block;}\n[data-reticle-hud] .reticle-send:hover{filter:brightness(1.12);}\n[data-reticle-hud] .reticle-send:active{transform:scale(.9);}\n[data-reticle-hud] .reticle-send:disabled{opacity:.4;cursor:default;}\n[data-reticle-hud] .reticle-banner{display:none;flex:none;padding:8px 15px;color:var(--reticle-accent);\n font-size:11.5px;font-weight:500;border-bottom:1px solid var(--reticle-line2);background:var(--reticle-accent-soft);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-banner]{display:block;}\n/* Export row: hidden during a live session; revealed when ended so the run can be copied/saved. */\n[data-reticle-hud] .reticle-export{display:none;align-items:center;gap:8px;margin-top:9px;}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud] .reticle-export{display:flex;}\n[data-reticle-hud] .reticle-export-msg{color:var(--reticle-ok);font-size:11px;opacity:0;transition:opacity .15s;}\n[data-reticle-hud] .reticle-export-msg[data-show=\"1\"]{opacity:1;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 3px rgba(246,180,76,.9),inset 0 0 30px 6px rgba(246,180,76,.4);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(61,215,166,.55);}\n/* Handoff tones tell the human the agent's mode at a glance. waiting = calm teal \"your turn\" (no\n alarm); ask = amber \"answer me\" with a pulse; warn = amber \"agent crashed\" with a pulse. Each leads\n the banner with an icon and overrides the calm ended-green accent. */\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-hud]{--reticle-accent:#38bdf8;--reticle-accent-soft:rgba(56,189,248,.16);}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]{font-weight:600;color:#7dd3fc;}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]::before{content:\"\\270B \";}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(56,189,248,.5);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-hud],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-hud]{--reticle-accent:#fb923c;--reticle-accent-soft:rgba(251,146,60,.18);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]{font-weight:600;color:#fdba74;}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner]::before{content:\"\\2753 \";}\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]::before{content:\"\\26A0\\FE0F \";}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-glow][data-on=\"1\"],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-glow][data-on=\"1\"]{animation:reticle-warn-pulse 1.5s ease-in-out infinite;\n box-shadow:inset 0 0 0 2px rgba(251,146,60,.7);}\n@keyframes reticle-warn-pulse{0%,100%{box-shadow:inset 0 0 0 2px rgba(251,146,60,.32);}\n 50%{box-shadow:inset 0 0 0 3px rgba(251,146,60,.85),inset 0 0 26px 5px rgba(251,146,60,.34);}}\n/* Replay-a-flow row: the human re-runs a saved flow with no agent. Hidden until flows are pushed.\n Bounded + self-scrolling: it sits between the flex:1 log and the flex:none composer, so without a\n height cap a long flow list would squeeze the log to nothing and push the message input past the\n panel's overflow:hidden clip. flex:none + max-height + overflow-y keep the log and input always\n visible; extra flow chips scroll inside this section instead of growing the panel. */\n[data-reticle-hud] .reticle-flows{display:none;flex:none;flex-wrap:wrap;align-content:flex-start;gap:6px;\n padding:9px 12px;border-top:1px solid var(--reticle-line2);max-height:88px;overflow-y:auto;overscroll-behavior:contain;}\n[data-reticle-hud] .reticle-flows[data-has=\"1\"]{display:flex;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-flows-cap{flex:0 0 100%;margin-bottom:1px;color:var(--reticle-faint);font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;}\n[data-reticle-hud] .reticle-flow{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;\n border-radius:7px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);color:var(--reticle-muted);\n font-family:var(--reticle-font);font-size:11px;font-weight:500;transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-flow:hover{color:var(--reticle-fg);background:var(--reticle-accent-soft);border-color:var(--reticle-accent);}\n[data-reticle-hud] .reticle-flow:active{transform:scale(.95);}\n";
5
+ /**
6
+ * The presenter overlay's stacking layer. Exported because the annotator has to sit ABOVE it: the
7
+ * overlay holds the page blocker, and anything below this number is covered by that shield - which
8
+ * is what buried the annotate composer and made its Cancel/Add buttons unclickable.
9
+ */
10
+ export declare const Z_OVERLAY = 2147483600;
11
+ export declare const PRESENTER_CSS: string;