@reticlehq/browser 2.7.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 (92) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +2 -2
  3. package/dist/actions/actions-dom.js +23 -15
  4. package/dist/actions/actions.d.ts +17 -0
  5. package/dist/actions/actions.js +132 -29
  6. package/dist/actions/synthetic-input.d.ts +9 -0
  7. package/dist/actions/synthetic-input.js +40 -0
  8. package/dist/commands/commands.js +18 -1
  9. package/dist/connect-options.d.ts +9 -9
  10. package/dist/dom/a11y.d.ts +9 -0
  11. package/dist/dom/a11y.js +19 -0
  12. package/dist/dom/desktop-capture.js +20 -3
  13. package/dist/dom/dom-ignore.d.ts +19 -6
  14. package/dist/dom/dom-ignore.js +20 -13
  15. package/dist/dom/query.d.ts +2 -2
  16. package/dist/dom/query.js +85 -45
  17. package/dist/dom/refs.d.ts +28 -0
  18. package/dist/dom/refs.js +87 -0
  19. package/dist/dom/snapshot.d.ts +5 -0
  20. package/dist/dom/snapshot.js +34 -0
  21. package/dist/dom/source.d.ts +14 -0
  22. package/dist/dom/source.js +16 -0
  23. package/dist/dom/theme.d.ts +33 -10
  24. package/dist/dom/theme.js +109 -50
  25. package/dist/observers/console.js +5 -2
  26. package/dist/observers/install-all.js +4 -1
  27. package/dist/observers/ipc.d.ts +16 -0
  28. package/dist/observers/ipc.js +19 -1
  29. package/dist/observers/network.d.ts +8 -0
  30. package/dist/observers/network.js +29 -6
  31. package/dist/observers/state.js +21 -2
  32. package/dist/presenter/presenter-brand.d.ts +24 -7
  33. package/dist/presenter/presenter-brand.js +23 -6
  34. package/dist/presenter/presenter-config.d.ts +102 -9
  35. package/dist/presenter/presenter-config.js +94 -8
  36. package/dist/presenter/presenter-controls.d.ts +10 -10
  37. package/dist/presenter/presenter-controls.js +175 -85
  38. package/dist/presenter/presenter-dock-layout.d.ts +6 -0
  39. package/dist/presenter/presenter-dock-layout.js +170 -0
  40. package/dist/presenter/presenter-drag.d.ts +48 -0
  41. package/dist/presenter/presenter-drag.js +198 -0
  42. package/dist/presenter/presenter-effects.d.ts +1 -1
  43. package/dist/presenter/presenter-effects.js +1 -1
  44. package/dist/presenter/presenter-glow.d.ts +5 -5
  45. package/dist/presenter/presenter-glow.js +7 -7
  46. package/dist/presenter/presenter-heroicons-data.d.ts +41 -0
  47. package/dist/presenter/presenter-heroicons-data.js +38 -0
  48. package/dist/presenter/presenter-hud-chrome.d.ts +17 -0
  49. package/dist/presenter/presenter-hud-chrome.js +41 -0
  50. package/dist/presenter/presenter-icons.d.ts +44 -0
  51. package/dist/presenter/presenter-icons.js +100 -0
  52. package/dist/presenter/presenter-log.d.ts +27 -5
  53. package/dist/presenter/presenter-log.js +114 -28
  54. package/dist/presenter/presenter-report-copy.d.ts +83 -0
  55. package/dist/presenter/presenter-report-copy.js +127 -0
  56. package/dist/presenter/presenter-report-styles.d.ts +7 -0
  57. package/dist/presenter/presenter-report-styles.js +102 -0
  58. package/dist/presenter/presenter-report.d.ts +23 -0
  59. package/dist/presenter/presenter-report.js +217 -0
  60. package/dist/presenter/presenter-run-state.d.ts +1 -1
  61. package/dist/presenter/presenter-settings-styles.d.ts +2 -0
  62. package/dist/presenter/presenter-settings-styles.js +153 -0
  63. package/dist/presenter/presenter-settings.d.ts +82 -0
  64. package/dist/presenter/presenter-settings.js +494 -0
  65. package/dist/presenter/presenter-shell-styles.d.ts +1 -0
  66. package/dist/presenter/presenter-shell-styles.js +375 -0
  67. package/dist/presenter/presenter-shell.d.ts +48 -0
  68. package/dist/presenter/presenter-shell.js +408 -0
  69. package/dist/presenter/presenter-styles.d.ts +9 -4
  70. package/dist/presenter/presenter-styles.js +61 -125
  71. package/dist/presenter/presenter-tally.d.ts +8 -11
  72. package/dist/presenter/presenter-tally.js +48 -23
  73. package/dist/presenter/presenter-test-helpers.d.ts +1 -1
  74. package/dist/presenter/presenter-test-helpers.js +1 -1
  75. package/dist/presenter/presenter-verbs.js +2 -2
  76. package/dist/presenter/presenter-workspace.d.ts +15 -0
  77. package/dist/presenter/presenter-workspace.js +180 -0
  78. package/dist/presenter/presenter.d.ts +15 -10
  79. package/dist/presenter/presenter.js +194 -46
  80. package/dist/registry/emitter.d.ts +2 -2
  81. package/dist/reticle.d.ts +2 -2
  82. package/dist/reticle.js +27 -25
  83. package/dist/review/annotator-styles.d.ts +6 -0
  84. package/dist/review/annotator-styles.js +81 -0
  85. package/dist/review/annotator.d.ts +29 -17
  86. package/dist/review/annotator.js +477 -146
  87. package/dist/timers/clock.js +8 -5
  88. package/dist/transport/unreachable-message.d.ts +12 -0
  89. package/dist/transport/unreachable-message.js +20 -0
  90. package/dist/util/captured-method.d.ts +37 -0
  91. package/dist/util/captured-method.js +45 -0
  92. package/package.json +9 -6
@@ -0,0 +1,198 @@
1
+ import { HUD_DOCK_MARGIN_PX, HUD_DRAG_IGNORE_SEL, HUD_DRAG_THRESHOLD_PX, HUD_DRAGGED_ATTR, HUD_POS_X_VAR, HUD_POS_Y_VAR, MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR, } from './presenter-config.js';
2
+ import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
3
+ const DRAG_HANDLE_DRAGGING_CLASS = 'reticle-drag-handle--dragging';
4
+ /** Whether the HUD is off the default bottom-right dock. */
5
+ export function isHudDragged(hud) {
6
+ return '1' === hud.getAttribute(HUD_DRAGGED_ATTR);
7
+ }
8
+ /** Clamp a HUD position so the full panel stays inside the viewport. */
9
+ export function clampHudPosition(left, top, width, height, viewportWidth, viewportHeight, margin = HUD_DOCK_MARGIN_PX) {
10
+ const minLeft = margin;
11
+ const minTop = margin;
12
+ const maxLeft = Math.max(minLeft, viewportWidth - width - margin);
13
+ const maxTop = Math.max(minTop, viewportHeight - height - margin);
14
+ return {
15
+ left: Math.max(minLeft, Math.min(left, maxLeft)),
16
+ top: Math.max(minTop, Math.min(top, maxTop)),
17
+ };
18
+ }
19
+ /** Paint an explicit left/top position (switches the HUD off the default bottom-right dock). */
20
+ export function applyHudPosition(hud, left, top) {
21
+ hud.setAttribute(HUD_DRAGGED_ATTR, '1');
22
+ hud.style.setProperty(HUD_POS_X_VAR, `${String(left)}px`);
23
+ hud.style.setProperty(HUD_POS_Y_VAR, `${String(top)}px`);
24
+ }
25
+ /** Return the HUD to the default bottom-right dock. */
26
+ export function resetHudDockPosition(hud) {
27
+ hud.removeAttribute(HUD_DRAGGED_ATTR);
28
+ hud.style.removeProperty(HUD_POS_X_VAR);
29
+ hud.style.removeProperty(HUD_POS_Y_VAR);
30
+ }
31
+ /** Return the HUD's laid-out box (rounded to whole pixels for stable clamping). */
32
+ export function hudLayoutBox(hud) {
33
+ const rect = hud.getBoundingClientRect();
34
+ return {
35
+ left: rect.left,
36
+ top: rect.top,
37
+ width: rect.width,
38
+ height: rect.height,
39
+ };
40
+ }
41
+ /** Read the dragged HUD's authored position (CSS vars), falling back to layout box. */
42
+ export function readHudPosition(hud) {
43
+ const x = hud.style.getPropertyValue(HUD_POS_X_VAR);
44
+ const y = hud.style.getPropertyValue(HUD_POS_Y_VAR);
45
+ if (x !== '' && y !== '') {
46
+ return { left: Number.parseFloat(x), top: Number.parseFloat(y) };
47
+ }
48
+ const rect = hud.getBoundingClientRect();
49
+ return { left: rect.left, top: rect.top };
50
+ }
51
+ /**
52
+ * Re-clamp a dragged HUD after resize, minimise/expand, or any layout-driven size change.
53
+ * No-op when the panel is still on the default dock.
54
+ */
55
+ export function relayoutHudPosition(hud) {
56
+ if (!isHudDragged(hud))
57
+ return;
58
+ const { left, top } = readHudPosition(hud);
59
+ const { width, height } = hudLayoutBox(hud);
60
+ const next = clampHudPosition(left, top, width, height, window.innerWidth, window.innerHeight);
61
+ applyHudPosition(hud, next.left, next.top);
62
+ }
63
+ /**
64
+ * Keep a dragged HUD inside the viewport when the window or panel size changes
65
+ * (minimise โ†” expand, tally appearing, viewport resize).
66
+ */
67
+ export function installHudPositionGuards(hud, overlay) {
68
+ const scheduleRelayout = () => {
69
+ scheduleSyncDockLayout(hud, overlay);
70
+ };
71
+ const onResize = () => scheduleRelayout();
72
+ window.addEventListener('resize', onResize);
73
+ let resizeObserver;
74
+ if (typeof ResizeObserver !== 'undefined') {
75
+ resizeObserver = new ResizeObserver(() => scheduleRelayout());
76
+ resizeObserver.observe(hud);
77
+ }
78
+ const minObserver = new MutationObserver(() => scheduleRelayout());
79
+ minObserver.observe(overlay, {
80
+ attributes: true,
81
+ attributeFilter: [MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR],
82
+ });
83
+ const onVisualViewportResize = () => scheduleRelayout();
84
+ window.visualViewport?.addEventListener('resize', onVisualViewportResize);
85
+ window.visualViewport?.addEventListener('scroll', onVisualViewportResize);
86
+ return () => {
87
+ window.removeEventListener('resize', onResize);
88
+ window.visualViewport?.removeEventListener('resize', onVisualViewportResize);
89
+ window.visualViewport?.removeEventListener('scroll', onVisualViewportResize);
90
+ resizeObserver?.disconnect();
91
+ minObserver.disconnect();
92
+ };
93
+ }
94
+ /**
95
+ * Drag the presenter HUD by its header. Returns a teardown that removes listeners.
96
+ * Uses pointer capture so the drag stays smooth even when the cursor leaves the head.
97
+ */
98
+ export function installHudDrag(hud, head, callbacks = {}) {
99
+ let dragging = false;
100
+ let startX = 0;
101
+ let startY = 0;
102
+ let startLeft = 0;
103
+ let startTop = 0;
104
+ let moved = false;
105
+ let activePointerId;
106
+ const finishDrag = () => {
107
+ const didMove = moved;
108
+ dragging = false;
109
+ head.classList.remove(DRAG_HANDLE_DRAGGING_CLASS);
110
+ activePointerId = undefined;
111
+ moved = false;
112
+ if (didMove) {
113
+ const overlay = hud.parentElement;
114
+ if (overlay instanceof HTMLElement)
115
+ scheduleSyncDockLayout(hud, overlay);
116
+ else
117
+ relayoutHudPosition(hud);
118
+ }
119
+ callbacks.onDragEnd?.(didMove);
120
+ };
121
+ const onPointerDown = (e) => {
122
+ if (e.button !== 0)
123
+ return;
124
+ const target = e.target;
125
+ if (!(target instanceof Element))
126
+ return;
127
+ if (target.closest(HUD_DRAG_IGNORE_SEL) !== null)
128
+ return;
129
+ const rect = hud.getBoundingClientRect();
130
+ startX = e.clientX;
131
+ startY = e.clientY;
132
+ startLeft = rect.left;
133
+ startTop = rect.top;
134
+ dragging = true;
135
+ moved = false;
136
+ activePointerId = e.pointerId;
137
+ head.classList.add(DRAG_HANDLE_DRAGGING_CLASS);
138
+ if ('function' === typeof head.setPointerCapture)
139
+ head.setPointerCapture(e.pointerId);
140
+ e.preventDefault();
141
+ };
142
+ const onPointerMove = (e) => {
143
+ if (!dragging || e.pointerId !== activePointerId)
144
+ return;
145
+ const dx = e.clientX - startX;
146
+ const dy = e.clientY - startY;
147
+ if (!moved && Math.abs(dx) < HUD_DRAG_THRESHOLD_PX && Math.abs(dy) < HUD_DRAG_THRESHOLD_PX)
148
+ return;
149
+ moved = true;
150
+ callbacks.onDragMove?.();
151
+ const box = hudLayoutBox(hud);
152
+ const next = clampHudPosition(startLeft + dx, startTop + dy, box.width, box.height, window.innerWidth, window.innerHeight);
153
+ applyHudPosition(hud, next.left, next.top);
154
+ e.preventDefault();
155
+ };
156
+ const releaseCapture = (pointerId) => {
157
+ if ('function' === typeof head.releasePointerCapture)
158
+ head.releasePointerCapture(pointerId);
159
+ };
160
+ const onPointerUp = (e) => {
161
+ if (!dragging || e.pointerId !== activePointerId)
162
+ return;
163
+ if (moved)
164
+ e.preventDefault();
165
+ releaseCapture(e.pointerId);
166
+ finishDrag();
167
+ };
168
+ const onPointerCancel = (e) => {
169
+ if (!dragging || e.pointerId !== activePointerId)
170
+ return;
171
+ releaseCapture(e.pointerId);
172
+ finishDrag();
173
+ };
174
+ head.addEventListener('pointerdown', onPointerDown);
175
+ head.addEventListener('pointermove', onPointerMove);
176
+ head.addEventListener('pointerup', onPointerUp);
177
+ head.addEventListener('pointercancel', onPointerCancel);
178
+ return () => {
179
+ head.removeEventListener('pointerdown', onPointerDown);
180
+ head.removeEventListener('pointermove', onPointerMove);
181
+ head.removeEventListener('pointerup', onPointerUp);
182
+ head.removeEventListener('pointercancel', onPointerCancel);
183
+ if (dragging)
184
+ releaseCapture(activePointerId ?? 0);
185
+ finishDrag();
186
+ };
187
+ }
188
+ /**
189
+ * Drag the presenter HUD from multiple handles (FAB when collapsed, toolbar brand when expanded).
190
+ * Returns a teardown that removes listeners from every handle.
191
+ */
192
+ export function installHudDragHandles(hud, handles, callbacks = {}) {
193
+ const teardowns = handles.map((handle) => installHudDrag(hud, handle, callbacks));
194
+ return () => {
195
+ for (const teardown of teardowns)
196
+ teardown();
197
+ };
198
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Synthetic-cursor + click effects โ€” the visible "the agent is acting here" motion. Pure DOM helpers
2
+ * Synthetic-cursor + click effects - the visible "the agent is acting here" motion. Pure DOM helpers
3
3
  * extracted from presenter.ts so the controller stays under the size cap; each is a no-op when its
4
4
  * element is absent (the panel may not be mounted). Behavior is unchanged from the inlined methods.
5
5
  */
@@ -1,6 +1,6 @@
1
1
  import { nativeSetTimeout } from '../timers/native-timers.js';
2
2
  /**
3
- * Synthetic-cursor + click effects โ€” the visible "the agent is acting here" motion. Pure DOM helpers
3
+ * Synthetic-cursor + click effects - the visible "the agent is acting here" motion. Pure DOM helpers
4
4
  * extracted from presenter.ts so the controller stays under the size cap; each is a no-op when its
5
5
  * element is absent (the panel may not be mounted). Behavior is unchanged from the inlined methods.
6
6
  */
@@ -10,12 +10,12 @@ interface GlowDeps {
10
10
  setMode: (mode: PresenterMode) => void;
11
11
  }
12
12
  /**
13
- * The glow / activity state machine โ€” drives the border shimmer + synthetic-cursor visibility from
13
+ * The glow / activity state machine - drives the border shimmer + synthetic-cursor visibility from
14
14
  * agent-activity timing. Extracted from presenter.ts so the controller file stays under the size cap;
15
15
  * behavior is byte-for-byte the same. It holds references to the same `#glow`/`#cursor` nodes the
16
- * Presenter does (both call setAttribute on those elements โ€” the Presenter owns the SESSION border via
16
+ * Presenter does (both call setAttribute on those elements - the Presenter owns the SESSION border via
17
17
  * data-on; this owns the BUSY shimmer via data-busy). A burst flips the glow IN once, holds it (the
18
- * slow reticle-pulse keeps breathing โ€” no per-action restart/strobe), then fades it OUT once after a quiet
18
+ * slow reticle-pulse keeps breathing - no per-action restart/strobe), then fades it OUT once after a quiet
19
19
  * window.
20
20
  */
21
21
  export declare class GlowController {
@@ -25,12 +25,12 @@ export declare class GlowController {
25
25
  setElements(glow: HTMLElement | undefined, cursor: HTMLElement | undefined): void;
26
26
  /** Current glow phase (test/diagnostic accessor). */
27
27
  phase(): GlowPhase;
28
- /** Last activity timestamp โ€” read by the Presenter's liveness heartbeat. */
28
+ /** Last activity timestamp - read by the Presenter's liveness heartbeat. */
29
29
  lastActivityMs(): number;
30
30
  /** Set the activity baseline WITHOUT entering busy (sessionStart / revive). */
31
31
  resetActivity(ms: number): void;
32
32
  /**
33
- * Record agent activity. Idempotent while busy โ€” only the first activity from idle/fading flips the
33
+ * Record agent activity. Idempotent while busy - only the first activity from idle/fading flips the
34
34
  * glow on (no strobe). `ms` lets log read the clock exactly once per row.
35
35
  */
36
36
  markActivity(ms?: number): void;
@@ -2,12 +2,12 @@ import { PresenterMode } from '@reticlehq/core';
2
2
  import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
3
3
  import { GlowPhase, BorderMode, DATA_BUSY, BUSY_ON, BUSY_OFF, GLOW_ON, GLOW_OFF, DATA_ON, } from './presenter-config.js';
4
4
  /**
5
- * The glow / activity state machine โ€” drives the border shimmer + synthetic-cursor visibility from
5
+ * The glow / activity state machine - drives the border shimmer + synthetic-cursor visibility from
6
6
  * agent-activity timing. Extracted from presenter.ts so the controller file stays under the size cap;
7
7
  * behavior is byte-for-byte the same. It holds references to the same `#glow`/`#cursor` nodes the
8
- * Presenter does (both call setAttribute on those elements โ€” the Presenter owns the SESSION border via
8
+ * Presenter does (both call setAttribute on those elements - the Presenter owns the SESSION border via
9
9
  * data-on; this owns the BUSY shimmer via data-busy). A burst flips the glow IN once, holds it (the
10
- * slow reticle-pulse keeps breathing โ€” no per-action restart/strobe), then fades it OUT once after a quiet
10
+ * slow reticle-pulse keeps breathing - no per-action restart/strobe), then fades it OUT once after a quiet
11
11
  * window.
12
12
  */
13
13
  export class GlowController {
@@ -38,7 +38,7 @@ export class GlowController {
38
38
  phase() {
39
39
  return this.#phase;
40
40
  }
41
- /** Last activity timestamp โ€” read by the Presenter's liveness heartbeat. */
41
+ /** Last activity timestamp - read by the Presenter's liveness heartbeat. */
42
42
  lastActivityMs() {
43
43
  return this.#lastActivityMs;
44
44
  }
@@ -47,7 +47,7 @@ export class GlowController {
47
47
  this.#lastActivityMs = ms;
48
48
  }
49
49
  /**
50
- * Record agent activity. Idempotent while busy โ€” only the first activity from idle/fading flips the
50
+ * Record agent activity. Idempotent while busy - only the first activity from idle/fading flips the
51
51
  * glow on (no strobe). `ms` lets log read the clock exactly once per row.
52
52
  */
53
53
  markActivity(ms = this.#now()) {
@@ -83,7 +83,7 @@ export class GlowController {
83
83
  // back-compat: the busy machine owns the base border.
84
84
  this.#glow?.setAttribute(DATA_ON, GLOW_ON);
85
85
  }
86
- // The HUD/log is session-owned (sessionStart/End), NOT toggled here โ€” it stays put on idle.
86
+ // The HUD/log is session-owned (sessionStart/End), NOT toggled here - it stays put on idle.
87
87
  this.#cursor?.setAttribute(DATA_ON, GLOW_ON);
88
88
  }
89
89
  #armIdleCheck() {
@@ -113,7 +113,7 @@ export class GlowController {
113
113
  // back-compat: fade the base border out.
114
114
  this.#glow?.setAttribute(DATA_ON, GLOW_OFF);
115
115
  }
116
- // Do NOT hide the HUD/log on idle โ€” it persists for the whole session (sessionStart/End).
116
+ // Do NOT hide the HUD/log on idle - it persists for the whole session (sessionStart/End).
117
117
  this.#cursor?.setAttribute(DATA_ON, GLOW_OFF);
118
118
  this.#setMode(PresenterMode.IDLE); // clear the READING/ACTING chip when going quiet
119
119
  this.#fadeTimer = nativeSetTimeout(() => {
@@ -0,0 +1,41 @@
1
+ /** Generated by scripts/embed-heroicons.mjs - do not edit by hand. */
2
+ /** Heroicons outline - MIT, https://heroicons.com */
3
+ declare const BODIES: {
4
+ view: string;
5
+ pointer: string;
6
+ send: string;
7
+ chart: string;
8
+ 'caret-down': string;
9
+ check: string;
10
+ remove: string;
11
+ pause: string;
12
+ play: string;
13
+ stop: string;
14
+ copy: string;
15
+ download: string;
16
+ message: string;
17
+ gear: string;
18
+ layout: string;
19
+ trash: string;
20
+ help: string;
21
+ 'caret-right': string;
22
+ annotate: string;
23
+ };
24
+ /**
25
+ * No `as const` on the values: it would inline every icon's path data into the emitted `.d.ts` as a
26
+ * literal type, publishing the whole icon set twice for type information no caller can use. The KEYS
27
+ * are still literal, which is the part `HeroIconBodyKey` needs.
28
+ */
29
+ export declare const HERO_ICON_BODIES: Record<keyof typeof BODIES, string>;
30
+ export type HeroIconBodyKey = keyof typeof BODIES;
31
+ /** Solid variants for toolbar toggles - shown when data-active="1". */
32
+ declare const SOLID_BODIES: {
33
+ chart: string;
34
+ annotate: string;
35
+ message: string;
36
+ view: string;
37
+ gear: string;
38
+ };
39
+ export declare const HERO_ICON_SOLID_BODIES: Record<keyof typeof SOLID_BODIES, string>;
40
+ export type HeroIconSolidBodyKey = keyof typeof SOLID_BODIES;
41
+ export {};
@@ -0,0 +1,38 @@
1
+ /** Generated by scripts/embed-heroicons.mjs - do not edit by hand. */
2
+ /** Heroicons outline - MIT, https://heroicons.com */
3
+ const BODIES = {
4
+ view: '<g><path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0"/><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7s-8.268-2.943-9.542-7"/></g>',
5
+ pointer: '<path d="M15.042 21.672L13.684 16.6m0 0l-2.51 2.225l.569-9.47l5.227 7.917l-3.286-.672ZM12 2.25V4.5m5.834.166l-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243l-1.59-1.59"/>',
6
+ send: '<path d="M6 12 3.269 3.126A59.768 59.768 0 0 1 21.485 12 59.77 59.77 0 0 1 3.27 20.876L5.999 12Zm0 0h7.5"/>',
7
+ chart: '<path d="M9 19v-6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2m0 0V9a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v10m-6 0a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2m0 0V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2"/>',
8
+ 'caret-down': '<path d="m19 9l-7 7l-7-7"/>',
9
+ check: '<path d="m5 13l4 4L19 7"/>',
10
+ remove: '<path d="m4.5 19.5l15-15m-15 0l15 15"/>',
11
+ pause: '<path d="M10 9v6m4-6v6m7-3a9 9 0 1 1-18 0a9 9 0 0 1 18 0"/>',
12
+ play: '<g><path d="m14.752 11.168l-3.197-2.132A1 1 0 0 0 10 9.87v4.263a1 1 0 0 0 1.555.832l3.197-2.132a1 1 0 0 0 0-1.664"/><path d="M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0"/></g>',
13
+ stop: '<g><path d="M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0"/><path d="M9 10a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1z"/></g>',
14
+ copy: '<path d="M8 7v8a2 2 0 0 0 2 2h6M8 7V5a2 2 0 0 1 2-2h4.586a1 1 0 0 1 .707.293l4.414 4.414a1 1 0 0 1 .293.707V15a2 2 0 0 1-2 2h-2M8 7H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2"/>',
15
+ download: '<path d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/>',
16
+ message: '<path d="M8.625 9.75a.375.375 0 1 1-.75 0a.375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0a.375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0a.375.375 0 0 1 .75 0Zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227c1.087.16 2.185.283 3.293.369V21l4.184-4.183a1.14 1.14 0 0 1 .778-.332a48.294 48.294 0 0 0 5.83-.498c1.585-.233 2.708-1.626 2.708-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"/>',
17
+ gear: '<g><path d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87c.074.04.147.083.22.127c.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 0 1 0 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 0 1 0-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124c.072-.044.146-.087.22-.128c.332-.183.582-.495.644-.869l.214-1.281Z"/><path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0Z"/></g>',
18
+ layout: '<path d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6Zm0 9.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6Zm0 9.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z"/>',
19
+ trash: '<path d="m19 7l-.867 12.142A2 2 0 0 1 16.138 21H7.862a2 2 0 0 1-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v3M4 7h16"/>',
20
+ help: '<path d="M8.228 9c.549-1.165 2.03-2 3.772-2c2.21 0 4 1.343 4 3c0 1.4-1.278 2.575-3.006 2.907c-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0"/>',
21
+ 'caret-right': '<path d="m9 5l7 7l-7 7"/>',
22
+ annotate: '<path d="m16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/>',
23
+ };
24
+ /**
25
+ * No `as const` on the values: it would inline every icon's path data into the emitted `.d.ts` as a
26
+ * literal type, publishing the whole icon set twice for type information no caller can use. The KEYS
27
+ * are still literal, which is the part `HeroIconBodyKey` needs.
28
+ */
29
+ export const HERO_ICON_BODIES = BODIES;
30
+ /** Solid variants for toolbar toggles - shown when data-active="1". */
31
+ const SOLID_BODIES = {
32
+ chart: '<path fill="currentColor" d="M2 11a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1zm6-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1zm6-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1z"/>',
33
+ annotate: '<path fill="currentColor" fill-rule="evenodd" d="m5.433 13.916l1.262-3.154a4 4 0 0 1 .885-1.343L14.5 2.5a2.121 2.121 0 1 1 3 3l-6.92 6.919c-.383.383-.84.684-1.343.885l-3.154 1.262a.5.5 0 0 1-.65-.65ZM2.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0 0 10 3H3.75A2.75 2.75 0 0 0 1 5.75v10.5A2.75 2.75 0 0 0 3.75 19h10.5A2.75 2.75 0 0 0 17 16.25V10a.75.75 0 0 0-1.5 0v6.25c0 .69-.56 1.25-1.25 1.25H3.75c-.69 0-1.25-.56-1.25-1.25V5.75Z" clip-rule="evenodd"/>',
34
+ message: '<path fill="currentColor" fill-rule="evenodd" d="M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902c.848.137 1.705.248 2.57.331v3.443a.75.75 0 0 0 1.28.53l3.58-3.579a.78.78 0 0 1 .527-.224a41.202 41.202 0 0 0 5.183-.5c1.437-.232 2.43-1.49 2.43-2.903V5.426c0-1.413-.993-2.67-2.43-2.902A41.289 41.289 0 0 0 10 2Zm0 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2ZM8 8a1 1 0 1 1-2 0a1 1 0 0 1 2 0Zm5 1a1 1 0 1 0 0-2a1 1 0 0 0 0 2Z" clip-rule="evenodd"/>',
35
+ view: '<g fill="currentColor"><path d="M10 12a2 2 0 1 0 0-4a2 2 0 0 0 0 4"/><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10M14 10a4 4 0 1 1-8 0a4 4 0 0 1 8 0" clip-rule="evenodd"/></g>',
36
+ gear: '<path fill="currentColor" fill-rule="evenodd" d="M7.84 1.804A1 1 0 0 1 8.82 1h2.36a1 1 0 0 1 .98.804l.331 1.652a6.993 6.993 0 0 1 1.929 1.115l1.598-.54a1 1 0 0 1 1.186.447l1.18 2.044a1 1 0 0 1-.205 1.251l-1.267 1.113a7.047 7.047 0 0 1 0 2.228l1.267 1.113a1 1 0 0 1 .206 1.25l-1.18 2.045a1 1 0 0 1-1.187.447l-1.598-.54a6.993 6.993 0 0 1-1.929 1.115l-.33 1.652a1 1 0 0 1-.98.804H8.82a1 1 0 0 1-.98-.804l-.331-1.652a6.993 6.993 0 0 1-1.929-1.115l-1.598.54a1 1 0 0 1-1.186-.447l-1.18-2.044a1 1 0 0 1 .205-1.251l1.267-1.114a7.05 7.05 0 0 1 0-2.227L1.821 7.773a1 1 0 0 1-.206-1.25l1.18-2.045a1 1 0 0 1 1.187-.447l1.598.54a6.993 6.993 0 0 1 1.93-1.115l.33-1.652ZM10 13a3 3 0 1 0 0-6a3 3 0 0 0 0 6Z" clip-rule="evenodd"/>',
37
+ };
38
+ export const HERO_ICON_SOLID_BODIES = SOLID_BODIES;
@@ -0,0 +1,17 @@
1
+ /** Shared polished-black surfaces used across HUD chrome panels. */
2
+ export declare const HUD_SURFACE_CLASS = "reticle-hud-surface";
3
+ /** Layered near-black base โ€” no embedded image assets. */
4
+ export declare const HUD_SURFACE_FILL: string;
5
+ export declare const HUD_SURFACE_PAINT: string;
6
+ export declare const HUD_DROP_SHADOW: string;
7
+ export declare const HUD_CHROME_CSS: string;
8
+ /** Inset well for the activity log โ€” sits inside the chat card. */
9
+ export declare const HUD_LOG_WELL_CLASS = "reticle-hud-log-well";
10
+ /**
11
+ * The activity feed sits ON the panel, not in a black box cut out of it.
12
+ *
13
+ * The inset well was a second surface inside a surface - two borders, two backgrounds - and it made
14
+ * the panel read as a container rather than as one card. It also grew and shrank with its content,
15
+ * so the panel resized on every row; the height is fixed now and the feed scrolls inside it.
16
+ */
17
+ export declare const HUD_LOG_WELL_CSS = "\n.reticle-hud-log-well{\n /* A settled height, but still allowed to shrink: pinned with flex:none it pushed the composer\n off the bottom of the panel the moment the rest of the content grew. */\n /* A flex column, so the feed inside actually FILLS the height: as a plain block the log was only\n as tall as its rows, which left the empty-state line floating near the top of an otherwise\n empty panel instead of centred in it. */\n position:relative;display:flex;flex-direction:column;\n flex:1 1 auto;height:210px;min-height:96px;max-height:210px;overflow:hidden;\n contain:layout style paint;\n margin:0 6px;border-radius:12px;background:transparent;}\n.reticle-hud-log-well > *{position:relative;z-index:1;}";
@@ -0,0 +1,41 @@
1
+ /** Shared polished-black surfaces used across HUD chrome panels. */
2
+ export const HUD_SURFACE_CLASS = 'reticle-hud-surface';
3
+ /** Layered near-black base โ€” no embedded image assets. */
4
+ export const HUD_SURFACE_FILL = `linear-gradient(165deg,#121218 0%,#09090c 42%,#000 100%)`;
5
+ export const HUD_SURFACE_PAINT = `background:${HUD_SURFACE_FILL};box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 0 0 1px rgba(255,255,255,.09);`;
6
+ export const HUD_DROP_SHADOW = '0 16px 40px rgba(0,0,0,.58),0 0 0 1px rgba(255,255,255,.08)';
7
+ export const HUD_CHROME_CSS = `
8
+ .${HUD_SURFACE_CLASS}{
9
+ position:relative;overflow:hidden;
10
+ contain:layout style paint;
11
+ ${HUD_SURFACE_PAINT}
12
+ }
13
+ .${HUD_SURFACE_CLASS}::before,
14
+ [data-reticle-hud] .reticle-hud-deco::after{
15
+ content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;z-index:1;
16
+ background:
17
+ radial-gradient(ellipse 120% 70% at 50% -18%,rgba(255,255,255,.07),transparent 52%),
18
+ linear-gradient(180deg,rgba(255,255,255,.035) 0%,transparent 36%,rgba(0,0,0,.12) 100%);
19
+ }
20
+ .${HUD_SURFACE_CLASS} > *{position:relative;z-index:2;}`;
21
+ /** Inset well for the activity log โ€” sits inside the chat card. */
22
+ export const HUD_LOG_WELL_CLASS = 'reticle-hud-log-well';
23
+ /**
24
+ * The activity feed sits ON the panel, not in a black box cut out of it.
25
+ *
26
+ * The inset well was a second surface inside a surface - two borders, two backgrounds - and it made
27
+ * the panel read as a container rather than as one card. It also grew and shrank with its content,
28
+ * so the panel resized on every row; the height is fixed now and the feed scrolls inside it.
29
+ */
30
+ export const HUD_LOG_WELL_CSS = `
31
+ .${HUD_LOG_WELL_CLASS}{
32
+ /* A settled height, but still allowed to shrink: pinned with flex:none it pushed the composer
33
+ off the bottom of the panel the moment the rest of the content grew. */
34
+ /* A flex column, so the feed inside actually FILLS the height: as a plain block the log was only
35
+ as tall as its rows, which left the empty-state line floating near the top of an otherwise
36
+ empty panel instead of centred in it. */
37
+ position:relative;display:flex;flex-direction:column;
38
+ flex:1 1 auto;height:210px;min-height:96px;max-height:210px;overflow:hidden;
39
+ contain:layout style paint;
40
+ margin:0 6px;border-radius:12px;background:transparent;}
41
+ .${HUD_LOG_WELL_CLASS} > *{position:relative;z-index:1;}`;
@@ -0,0 +1,44 @@
1
+ import { type HeroIconBodyKey, type HeroIconSolidBodyKey } from './presenter-heroicons-data.js';
2
+ /** Presenter icon keys used by the HUD. */
3
+ export declare const PresenterIcon: {
4
+ readonly VIEW: "view";
5
+ readonly POINTER: "pointer";
6
+ readonly SEND: "send";
7
+ readonly CHART: "chart";
8
+ readonly CARET_DOWN: "caret-down";
9
+ readonly CHECK: "check";
10
+ readonly REMOVE: "remove";
11
+ readonly PAUSE: "pause";
12
+ readonly PLAY: "play";
13
+ readonly STOP: "stop";
14
+ readonly COPY: "copy";
15
+ readonly DOWNLOAD: "download";
16
+ readonly MESSAGE: "message";
17
+ readonly GEAR: "gear";
18
+ readonly LAYOUT: "layout";
19
+ readonly TRASH: "trash";
20
+ readonly HELP: "help";
21
+ readonly CARET_RIGHT: "caret-right";
22
+ readonly ANNOTATE: "annotate";
23
+ };
24
+ export type PresenterIconName = (typeof PresenterIcon)[keyof typeof PresenterIcon];
25
+ /** Shared HUD icon sizes (px) - one knob for visual density. */
26
+ export declare const PRESENTER_ICON_SIZE: {
27
+ readonly CHIP: 11;
28
+ readonly LOG: 11;
29
+ readonly TALLY: 12;
30
+ readonly CTL: 10;
31
+ readonly SEND: 14;
32
+ readonly MIN: 12;
33
+ readonly HELP: 12;
34
+ readonly TOOLBAR: 18;
35
+ readonly FAB: 22;
36
+ };
37
+ /** Create an inline Heroicons outline SVG (24px grid, currentColor stroke). */
38
+ export declare function hiIcon(name: HeroIconBodyKey, sizePx?: number): HTMLSpanElement;
39
+ /** Replace a parent's children with a Heroicon. */
40
+ export declare function setHiIcon(parent: HTMLElement, name: HeroIconBodyKey, sizePx?: number): void;
41
+ /** Inline SVG markup for static HUD templates - survives mount order and dep-cache staleness. */
42
+ export declare function hiIconHtml(name: HeroIconBodyKey, sizePx?: number): string;
43
+ /** Outline + solid pair for toolbar toggles - solid layer shows when the button is active. */
44
+ export declare function hiToggleIconHtml(name: HeroIconSolidBodyKey, sizePx?: number): string;
@@ -0,0 +1,100 @@
1
+ import { HERO_ICON_BODIES, HERO_ICON_SOLID_BODIES, } from './presenter-heroicons-data.js';
2
+ /** Presenter icon keys used by the HUD. */
3
+ export const PresenterIcon = {
4
+ VIEW: 'view',
5
+ POINTER: 'pointer',
6
+ SEND: 'send',
7
+ CHART: 'chart',
8
+ CARET_DOWN: 'caret-down',
9
+ CHECK: 'check',
10
+ REMOVE: 'remove',
11
+ PAUSE: 'pause',
12
+ PLAY: 'play',
13
+ STOP: 'stop',
14
+ COPY: 'copy',
15
+ DOWNLOAD: 'download',
16
+ MESSAGE: 'message',
17
+ GEAR: 'gear',
18
+ LAYOUT: 'layout',
19
+ TRASH: 'trash',
20
+ HELP: 'help',
21
+ CARET_RIGHT: 'caret-right',
22
+ ANNOTATE: 'annotate',
23
+ };
24
+ /** Shared HUD icon sizes (px) - one knob for visual density. */
25
+ export const PRESENTER_ICON_SIZE = {
26
+ CHIP: 11,
27
+ LOG: 11,
28
+ TALLY: 12,
29
+ CTL: 10,
30
+ SEND: 14,
31
+ MIN: 12,
32
+ HELP: 12,
33
+ TOOLBAR: 18,
34
+ FAB: 22,
35
+ };
36
+ const SVG_NS = 'http://www.w3.org/2000/svg';
37
+ const HERO_VIEWBOX = '0 0 24 24';
38
+ const svgCache = new Map();
39
+ function buildSvg(name, sizePx) {
40
+ const cacheKey = `${name}:${String(sizePx)}`;
41
+ const cached = svgCache.get(cacheKey);
42
+ if (cached !== undefined) {
43
+ return document.importNode(cached, true);
44
+ }
45
+ const body = HERO_ICON_BODIES[name];
46
+ if (body === undefined) {
47
+ throw new Error(`missing heroicon body: ${String(name)}`);
48
+ }
49
+ const parsed = new DOMParser().parseFromString(`<svg xmlns="${SVG_NS}" viewBox="${HERO_VIEWBOX}" fill="none">${body}</svg>`, 'image/svg+xml');
50
+ const root = parsed.documentElement;
51
+ const svg = root instanceof SVGSVGElement
52
+ ? document.importNode(root, true)
53
+ : document.createElementNS(SVG_NS, 'svg');
54
+ svg.setAttribute('viewBox', HERO_VIEWBOX);
55
+ svg.setAttribute('width', String(sizePx));
56
+ svg.setAttribute('height', String(sizePx));
57
+ svg.setAttribute('fill', 'none');
58
+ if (!(root instanceof SVGSVGElement)) {
59
+ svg.innerHTML = body;
60
+ }
61
+ svgCache.set(cacheKey, svg);
62
+ return document.importNode(svg, true);
63
+ }
64
+ /** Create an inline Heroicons outline SVG (24px grid, currentColor stroke). */
65
+ export function hiIcon(name, sizePx = 16) {
66
+ const wrap = document.createElement('span');
67
+ wrap.className = 'reticle-hi-icon';
68
+ wrap.setAttribute('aria-hidden', 'true');
69
+ wrap.style.width = `${String(sizePx)}px`;
70
+ wrap.style.height = `${String(sizePx)}px`;
71
+ wrap.appendChild(buildSvg(name, sizePx));
72
+ return wrap;
73
+ }
74
+ /** Replace a parent's children with a Heroicon. */
75
+ export function setHiIcon(parent, name, sizePx = 16) {
76
+ parent.replaceChildren(hiIcon(name, sizePx));
77
+ }
78
+ /** Inline SVG markup for static HUD templates - survives mount order and dep-cache staleness. */
79
+ export function hiIconHtml(name, sizePx = 16) {
80
+ const body = HERO_ICON_BODIES[name];
81
+ if (body === undefined) {
82
+ throw new Error(`missing heroicon body: ${String(name)}`);
83
+ }
84
+ return `<span class="reticle-hi-icon" aria-hidden="true" style="width:${String(sizePx)}px;height:${String(sizePx)}px"><svg xmlns="${SVG_NS}" viewBox="${HERO_VIEWBOX}" width="${String(sizePx)}" height="${String(sizePx)}" fill="none">${body}</svg></span>`;
85
+ }
86
+ /** Outline + solid pair for toolbar toggles - solid layer shows when the button is active. */
87
+ export function hiToggleIconHtml(name, sizePx = 16) {
88
+ const outline = HERO_ICON_BODIES[name];
89
+ const solid = HERO_ICON_SOLID_BODIES[name];
90
+ if (outline === undefined || solid === undefined) {
91
+ throw new Error(`missing heroicon toggle pair: ${String(name)}`);
92
+ }
93
+ const size = String(sizePx);
94
+ return (`<span class="reticle-hi-toggle" aria-hidden="true" style="width:${size}px;height:${size}px">` +
95
+ `<span class="reticle-hi-icon reticle-hi-icon--outline" style="width:${size}px;height:${size}px">` +
96
+ `<svg xmlns="${SVG_NS}" viewBox="${HERO_VIEWBOX}" width="${size}" height="${size}" fill="none">${outline}</svg></span>` +
97
+ `<span class="reticle-hi-icon reticle-hi-icon--solid" style="width:${size}px;height:${size}px">` +
98
+ `<svg xmlns="${SVG_NS}" viewBox="${HERO_VIEWBOX}" width="${size}" height="${size}" fill="currentColor">${solid}</svg></span>` +
99
+ `</span>`);
100
+ }
@@ -7,19 +7,41 @@ export declare const LOG_KIND: {
7
7
  readonly HUMAN: "human";
8
8
  };
9
9
  export type LogKind = (typeof LOG_KIND)[keyof typeof LOG_KIND];
10
- /** Prefix for a human-authored activity row ("๐Ÿง‘ you: <text>"). Presenter-only UI. */
11
- export declare const HUMAN_ROW_PREFIX = "\uD83E\uDDD1 you: ";
12
10
  /** Act-row outcome glyph keys (presenter-only UI). */
13
11
  export declare const LOG_RESULT: {
14
12
  readonly PASS: "pass";
15
13
  readonly FAIL: "fail";
16
14
  };
17
15
  export type LogResult = (typeof LOG_RESULT)[keyof typeof LOG_RESULT];
18
- /** HUD chip copy keyed by presenter mode (UI text, browser-local โ€” not a wire string). */
16
+ /** HUD chip copy keyed by presenter mode (UI text, browser-local - not a wire string). */
19
17
  export declare const CHIP_LABEL: Record<PresenterMode, string>;
20
18
  export declare const DATA_RETICLE_LOG = "data-reticle-log";
19
+ /**
20
+ * The attribute on ONE log line's timestamp. Deliberately not `data-reticle-log-ts`: that name is
21
+ * the overlay-level SETTING (show timestamps y/n) and the overlay root carries it, so a bare
22
+ * `[data-reticle-log-ts]{opacity:.85}` here matched the overlay itself and made the entire HUD -
23
+ * chat panel included - 85% transparent, with the page legible straight through it.
24
+ */
25
+ export declare const LOG_TIME_ATTR = "data-reticle-log-time";
21
26
  /** CSS for the log feed (injected with the rest of the presenter stylesheet; vars inherit from the card). */
22
- export declare const LOG_CSS = "\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";
27
+ export declare const LOG_CSS: string;
28
+ /**
29
+ * Pin the feed to its newest row.
30
+ *
31
+ * Called on append AND when the chat is opened, because a hidden panel has no layout: rows added
32
+ * while it was minimised could not scroll it, so opening it showed the OLDEST row and the latest
33
+ * activity - the reason to open it - was somewhere below the fold.
34
+ */
35
+ export declare function scrollLogToLatest(container: HTMLElement): void;
36
+ /**
37
+ * Pin the feed to its newest row once the panel has actually laid out.
38
+ *
39
+ * A single frame is not enough on open: the rows carry `content-visibility:auto`, so their real
40
+ * heights arrive after the first paint and `scrollHeight` keeps growing under a scroll that has
41
+ * already happened - the feed landed a third of the way down instead of at the end. Two frames
42
+ * plus one settle pass costs nothing and lands on the last row.
43
+ */
44
+ export declare function settleLogAtLatest(container: HTMLElement): void;
23
45
  /** Handle returned from logRow/Presenter.log so the caller can stamp the outcome glyph later. */
24
46
  export interface LogHandle {
25
47
  result(r: LogResult): void;
@@ -27,7 +49,7 @@ export interface LogHandle {
27
49
  /** Clamp a logMax option to a sane positive integer, falling back to the default. */
28
50
  export declare function clampLogMax(n: number | undefined): number;
29
51
  /**
30
- * Pure, human-readable duration ("3s", "47s", "2m", "1h 4m") โ€” no clock read, so it stays
52
+ * Pure, human-readable duration ("3s", "47s", "2m", "1h 4m") - no clock read, so it stays
31
53
  * deterministic in tests. Used for both the per-row timestamp (time since session start) and the
32
54
  * live "idle ยท {duration} since last action" heartbeat. Sub-second reads as "0s".
33
55
  */