@reticlehq/browser 2.13.1 → 3.1.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 (244) hide show
  1. package/dist/actions/actions-dom.d.ts +8 -20
  2. package/dist/actions/actions-dom.js +70 -69
  3. package/dist/actions/actions-press.d.ts +14 -0
  4. package/dist/actions/actions-press.js +123 -0
  5. package/dist/actions/actions.d.ts +1 -89
  6. package/dist/actions/actions.js +64 -365
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +6 -36
  9. package/dist/actions/danger-context.d.ts +3 -0
  10. package/dist/actions/danger-context.js +39 -0
  11. package/dist/actions/scroll.d.ts +8 -10
  12. package/dist/actions/scroll.js +28 -17
  13. package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
  14. package/dist/actions/synthetic/synthetic-input.js +13 -0
  15. package/dist/actions/value-input.d.ts +0 -30
  16. package/dist/actions/value-input.js +0 -33
  17. package/dist/commands/commands.d.ts +0 -2
  18. package/dist/commands/commands.js +10 -70
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +3 -90
  22. package/dist/dom/addressing/auto-anchor.d.ts +25 -0
  23. package/dist/dom/addressing/auto-anchor.js +41 -0
  24. package/dist/dom/addressing/refs.d.ts +14 -0
  25. package/dist/dom/addressing/refs.js +114 -0
  26. package/dist/dom/addressing/source.d.ts +9 -0
  27. package/dist/dom/addressing/source.js +29 -0
  28. package/dist/dom/chart.d.ts +0 -61
  29. package/dist/dom/chart.js +0 -60
  30. package/dist/dom/desktop-capture.d.ts +0 -30
  31. package/dist/dom/desktop-capture.js +1 -73
  32. package/dist/dom/dom-ignore.d.ts +0 -30
  33. package/dist/dom/dom-ignore.js +1 -48
  34. package/dist/dom/occlusion.d.ts +0 -7
  35. package/dist/dom/occlusion.js +31 -12
  36. package/dist/dom/paint-context.d.ts +6 -0
  37. package/dist/dom/paint-context.js +38 -0
  38. package/dist/dom/query.d.ts +0 -14
  39. package/dist/dom/query.js +50 -216
  40. package/dist/dom/realm.d.ts +0 -20
  41. package/dist/dom/realm.js +0 -28
  42. package/dist/dom/shadow-registry.d.ts +0 -33
  43. package/dist/dom/shadow-registry.js +2 -72
  44. package/dist/dom/snapshot.d.ts +2 -43
  45. package/dist/dom/snapshot.js +27 -152
  46. package/dist/dom/theme.d.ts +0 -37
  47. package/dist/dom/theme.js +2 -52
  48. package/dist/dom/virtualized.d.ts +0 -17
  49. package/dist/dom/virtualized.js +0 -27
  50. package/dist/edit-epoch.d.ts +1 -23
  51. package/dist/edit-epoch.js +1 -53
  52. package/dist/index.d.ts +4 -5
  53. package/dist/index.js +4 -28
  54. package/dist/observers/animation.d.ts +0 -1
  55. package/dist/observers/animation.js +1 -4
  56. package/dist/observers/blind-spots.d.ts +0 -26
  57. package/dist/observers/blind-spots.js +3 -62
  58. package/dist/observers/console.d.ts +1 -2
  59. package/dist/observers/console.js +41 -30
  60. package/dist/observers/context-open.d.ts +1 -11
  61. package/dist/observers/context-open.js +10 -14
  62. package/dist/observers/departure.d.ts +3 -0
  63. package/dist/observers/departure.js +52 -0
  64. package/dist/observers/dialogs.d.ts +2 -0
  65. package/dist/observers/dialogs.js +39 -0
  66. package/dist/observers/dom.js +28 -52
  67. package/dist/observers/download.d.ts +1 -2
  68. package/dist/observers/download.js +39 -56
  69. package/dist/observers/focus.d.ts +0 -6
  70. package/dist/observers/focus.js +0 -7
  71. package/dist/observers/frames.d.ts +0 -9
  72. package/dist/observers/frames.js +1 -28
  73. package/dist/observers/health.d.ts +0 -5
  74. package/dist/observers/health.js +2 -34
  75. package/dist/observers/install-all.d.ts +2 -0
  76. package/dist/observers/install-all.js +26 -27
  77. package/dist/observers/ipc.d.ts +0 -25
  78. package/dist/observers/ipc.js +0 -65
  79. package/dist/observers/navigation.d.ts +2 -0
  80. package/dist/observers/navigation.js +33 -0
  81. package/dist/observers/net-detail/network-body.d.ts +12 -0
  82. package/dist/observers/net-detail/network-body.js +71 -0
  83. package/dist/observers/net-detail/network-redact.d.ts +1 -0
  84. package/dist/observers/net-detail/network-redact.js +1 -0
  85. package/dist/observers/network-stream.d.ts +0 -21
  86. package/dist/observers/network-stream.js +3 -46
  87. package/dist/observers/network.d.ts +3 -27
  88. package/dist/observers/network.js +178 -295
  89. package/dist/observers/perf.d.ts +0 -10
  90. package/dist/observers/perf.js +0 -16
  91. package/dist/observers/route.d.ts +1 -2
  92. package/dist/observers/route.js +13 -19
  93. package/dist/observers/scroll.d.ts +0 -1
  94. package/dist/observers/scroll.js +2 -11
  95. package/dist/observers/sdk-failure.d.ts +1 -9
  96. package/dist/observers/sdk-failure.js +1 -30
  97. package/dist/observers/state.d.ts +1 -11
  98. package/dist/observers/state.js +13 -41
  99. package/dist/observers/storage.d.ts +0 -14
  100. package/dist/observers/storage.js +10 -34
  101. package/dist/observers/types.d.ts +0 -15
  102. package/dist/observers/types.js +0 -14
  103. package/dist/patching/capture-method.d.ts +0 -24
  104. package/dist/patching/capture-method.js +0 -26
  105. package/dist/presenter/chrome/layers.d.ts +2 -0
  106. package/dist/presenter/chrome/layers.js +2 -0
  107. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  108. package/dist/presenter/chrome/log-kinds.js +7 -0
  109. package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
  110. package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
  111. package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
  112. package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
  113. package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
  114. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
  115. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
  116. package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
  117. package/dist/presenter/chrome/presenter-log.d.ts +17 -0
  118. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
  119. package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
  120. package/dist/presenter/chrome/presenter-report-copy.js +95 -0
  121. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  122. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  123. package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
  124. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
  125. package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
  126. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
  127. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
  128. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
  129. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
  130. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  131. package/dist/presenter/presenter-account-styles.js +99 -0
  132. package/dist/presenter/presenter-account.d.ts +40 -0
  133. package/dist/presenter/presenter-account.js +178 -0
  134. package/dist/presenter/presenter-config.d.ts +10 -107
  135. package/dist/presenter/presenter-config.js +5 -92
  136. package/dist/presenter/presenter-controls.d.ts +1 -35
  137. package/dist/presenter/presenter-controls.js +10 -133
  138. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  139. package/dist/presenter/presenter-dock-layout.js +1 -5
  140. package/dist/presenter/presenter-drag.d.ts +2 -42
  141. package/dist/presenter/presenter-drag.js +3 -76
  142. package/dist/presenter/presenter-glow.d.ts +0 -21
  143. package/dist/presenter/presenter-glow.js +2 -28
  144. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  145. package/dist/presenter/presenter-hud-position.js +50 -0
  146. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  147. package/dist/presenter/presenter-report-styles.js +22 -7
  148. package/dist/presenter/presenter-report.d.ts +5 -7
  149. package/dist/presenter/presenter-report.js +44 -95
  150. package/dist/presenter/presenter-run-state.d.ts +1 -8
  151. package/dist/presenter/presenter-run-state.js +1 -6
  152. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  153. package/dist/presenter/presenter-settings-styles.js +2 -2
  154. package/dist/presenter/presenter-settings.d.ts +5 -23
  155. package/dist/presenter/presenter-settings.js +21 -33
  156. package/dist/presenter/presenter-shell-styles.js +23 -20
  157. package/dist/presenter/presenter-shell.d.ts +6 -18
  158. package/dist/presenter/presenter-shell.js +26 -77
  159. package/dist/presenter/presenter-styles.d.ts +2 -10
  160. package/dist/presenter/presenter-styles.js +6 -12
  161. package/dist/presenter/presenter-workspace.d.ts +4 -4
  162. package/dist/presenter/presenter-workspace.js +30 -10
  163. package/dist/presenter/presenter.d.ts +3 -63
  164. package/dist/presenter/presenter.js +29 -132
  165. package/dist/presenter/tour/tour-view.d.ts +33 -0
  166. package/dist/presenter/tour/tour-view.js +188 -0
  167. package/dist/presenter/tour/tour.d.ts +25 -0
  168. package/dist/presenter/tour/tour.js +245 -0
  169. package/dist/recorder/recorder-styles.d.ts +0 -6
  170. package/dist/recorder/recorder-styles.js +0 -6
  171. package/dist/recorder/recorder.d.ts +0 -34
  172. package/dist/recorder/recorder.js +6 -49
  173. package/dist/registry/auto-testids.d.ts +0 -19
  174. package/dist/registry/auto-testids.js +1 -21
  175. package/dist/registry/capabilities.d.ts +0 -31
  176. package/dist/registry/capabilities.js +1 -46
  177. package/dist/registry/commit-and-signal.d.ts +0 -6
  178. package/dist/registry/commit-and-signal.js +1 -7
  179. package/dist/registry/declared-channels.d.ts +2 -0
  180. package/dist/registry/declared-channels.js +14 -0
  181. package/dist/registry/declared-platform.d.ts +4 -0
  182. package/dist/registry/declared-platform.js +34 -0
  183. package/dist/registry/domains.d.ts +0 -8
  184. package/dist/registry/domains.js +0 -7
  185. package/dist/registry/emitter.d.ts +0 -14
  186. package/dist/registry/emitter.js +1 -11
  187. package/dist/registry/store-adapters.d.ts +0 -186
  188. package/dist/registry/store-adapters.js +1 -164
  189. package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
  190. package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
  191. package/dist/registry/stores.d.ts +0 -33
  192. package/dist/registry/stores.js +1 -60
  193. package/dist/reticle-presenter-helpers.d.ts +0 -13
  194. package/dist/reticle-presenter-helpers.js +10 -20
  195. package/dist/reticle-singleton.d.ts +2 -0
  196. package/dist/reticle-singleton.js +3 -0
  197. package/dist/reticle.d.ts +1 -52
  198. package/dist/reticle.js +115 -180
  199. package/dist/review/annotator-styles.js +1 -8
  200. package/dist/review/annotator.d.ts +2 -13
  201. package/dist/review/annotator.js +2 -42
  202. package/dist/review/mark-anchor.d.ts +0 -11
  203. package/dist/review/mark-anchor.js +3 -11
  204. package/dist/security/serialization.d.ts +0 -20
  205. package/dist/security/serialization.js +1 -66
  206. package/dist/session-continuity.d.ts +0 -16
  207. package/dist/session-continuity.js +1 -20
  208. package/dist/timers/clock.d.ts +1 -14
  209. package/dist/timers/clock.js +59 -60
  210. package/dist/timers/console/native-console.js +5 -0
  211. package/dist/timers/native/native-timers.d.ts +11 -0
  212. package/dist/timers/native/native-timers.js +48 -0
  213. package/dist/transport/transport.d.ts +0 -51
  214. package/dist/transport/transport.js +5 -131
  215. package/dist/transport/unreachable-message.d.ts +0 -17
  216. package/dist/transport/unreachable-message.js +0 -17
  217. package/dist/util/captured-method.d.ts +0 -35
  218. package/dist/util/captured-method.js +0 -35
  219. package/package.json +5 -4
  220. package/dist/actions/synthetic-input.d.ts +0 -9
  221. package/dist/actions/synthetic-input.js +0 -40
  222. package/dist/dom/auto-anchor.d.ts +0 -65
  223. package/dist/dom/auto-anchor.js +0 -74
  224. package/dist/dom/refs.d.ts +0 -90
  225. package/dist/dom/refs.js +0 -245
  226. package/dist/dom/source.d.ts +0 -53
  227. package/dist/dom/source.js +0 -74
  228. package/dist/observers/network-body.d.ts +0 -35
  229. package/dist/observers/network-body.js +0 -168
  230. package/dist/observers/network-redact.d.ts +0 -20
  231. package/dist/observers/network-redact.js +0 -80
  232. package/dist/presenter/presenter-brand.d.ts +0 -22
  233. package/dist/presenter/presenter-effects.d.ts +0 -13
  234. package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
  235. package/dist/presenter/presenter-log.d.ts +0 -64
  236. package/dist/presenter/presenter-report-copy.d.ts +0 -112
  237. package/dist/presenter/presenter-report-copy.js +0 -156
  238. package/dist/presenter/presenter-test-helpers.d.ts +0 -16
  239. package/dist/presenter/presenter-test-helpers.js +0 -41
  240. package/dist/timers/native-console.js +0 -9
  241. package/dist/timers/native-timers.d.ts +0 -36
  242. package/dist/timers/native-timers.js +0 -78
  243. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  244. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
@@ -1,75 +1,12 @@
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';
1
+ import { HUD_DRAG_IGNORE_SEL, HUD_DRAG_THRESHOLD_PX, MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR, } from './presenter-config.js';
2
2
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
3
+ import { applyHudPosition, clampHudPosition, hudLayoutBox, relayoutHudPosition, } from './presenter-hud-position.js';
4
+ export { applyHudPosition, isHudDragged, readHudPosition, relayoutHudPosition, clampHudPosition, hudLayoutBox, resetHudDockPosition, } from './presenter-hud-position.js';
3
5
  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
6
  export function installHudPositionGuards(hud, overlay) {
68
7
  const scheduleRelayout = () => {
69
8
  scheduleSyncDockLayout(hud, overlay);
70
9
  };
71
- // One signal for all three listeners here. The observers below are NOT covered — neither
72
- // ResizeObserver nor MutationObserver accepts one — so they keep their explicit disconnects.
73
10
  const listeners = new AbortController();
74
11
  const { signal } = listeners;
75
12
  const onResize = () => scheduleRelayout();
@@ -93,10 +30,6 @@ export function installHudPositionGuards(hud, overlay) {
93
30
  minObserver.disconnect();
94
31
  };
95
32
  }
96
- /**
97
- * Drag the presenter HUD by its header. Returns a teardown that removes listeners.
98
- * Uses pointer capture so the drag stays smooth even when the cursor leaves the head.
99
- */
100
33
  export function installHudDrag(hud, head, callbacks = {}) {
101
34
  let dragging = false;
102
35
  let startX = 0;
@@ -173,8 +106,6 @@ export function installHudDrag(hud, head, callbacks = {}) {
173
106
  releaseCapture(e.pointerId);
174
107
  finishDrag();
175
108
  };
176
- // All four pointer phases share one signal: they are added together and must come off
177
- // together, and a partial removal would leave a half-live drag.
178
109
  const listeners = new AbortController();
179
110
  const { signal } = listeners;
180
111
  head.addEventListener('pointerdown', onPointerDown, { signal });
@@ -188,10 +119,6 @@ export function installHudDrag(hud, head, callbacks = {}) {
188
119
  finishDrag();
189
120
  };
190
121
  }
191
- /**
192
- * Drag the presenter HUD from multiple handles (FAB when collapsed, toolbar brand when expanded).
193
- * Returns a teardown that removes listeners from every handle.
194
- */
195
122
  export function installHudDragHandles(hud, handles, callbacks = {}) {
196
123
  const teardowns = handles.map((handle) => installHudDrag(hud, handle, callbacks));
197
124
  return () => {
@@ -1,42 +1,21 @@
1
1
  import { PresenterMode } from '@reticlehq/core';
2
2
  import { GlowPhase, BorderMode } from './presenter-config.js';
3
- /** What the controller needs from the Presenter: the clock, the timing knobs, and a setMode callback. */
4
3
  interface GlowDeps {
5
4
  now: () => number;
6
5
  idleAfterMs: number;
7
6
  glowFadeMs: number;
8
7
  borderMode: BorderMode;
9
- /** Called when the glow fades to idle, so the Presenter clears the READING/ACTING chip. */
10
8
  setMode: (mode: PresenterMode) => void;
11
9
  }
12
- /**
13
- * The glow / activity state machine - drives the border shimmer + synthetic-cursor visibility from
14
- * agent-activity timing. Extracted from presenter.ts so the controller file stays under the size cap;
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
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
19
- * window.
20
- */
21
10
  export declare class GlowController {
22
11
  #private;
23
12
  constructor(deps: GlowDeps);
24
- /** Wire the glow + cursor elements after the Presenter mounts the DOM. */
25
13
  setElements(glow: HTMLElement | undefined, cursor: HTMLElement | undefined): void;
26
- /** Current glow phase (test/diagnostic accessor). */
27
14
  phase(): GlowPhase;
28
- /** Last activity timestamp - read by the Presenter's liveness heartbeat. */
29
15
  lastActivityMs(): number;
30
- /** Set the activity baseline WITHOUT entering busy (sessionStart / revive). */
31
16
  resetActivity(ms: number): void;
32
- /**
33
- * Record agent activity. Idempotent while busy - only the first activity from idle/fading flips the
34
- * glow on (no strobe). `ms` lets log read the clock exactly once per row.
35
- */
36
17
  markActivity(ms?: number): void;
37
- /** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
38
18
  scheduleIdle(): void;
39
- /** Clear both timers (called from Presenter.destroy). */
40
19
  teardown(): void;
41
20
  }
42
21
  export {};
@@ -1,15 +1,6 @@
1
1
  import { PresenterMode } from '@reticlehq/core';
2
- import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
2
+ import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/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
- /**
5
- * The glow / activity state machine - drives the border shimmer + synthetic-cursor visibility from
6
- * agent-activity timing. Extracted from presenter.ts so the controller file stays under the size cap;
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
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
11
- * window.
12
- */
13
4
  export class GlowController {
14
5
  #phase = GlowPhase.IDLE;
15
6
  #lastActivityMs = 0;
@@ -29,38 +20,28 @@ export class GlowController {
29
20
  this.#borderMode = deps.borderMode;
30
21
  this.#setMode = deps.setMode;
31
22
  }
32
- /** Wire the glow + cursor elements after the Presenter mounts the DOM. */
33
23
  setElements(glow, cursor) {
34
24
  this.#glow = glow;
35
25
  this.#cursor = cursor;
36
26
  }
37
- /** Current glow phase (test/diagnostic accessor). */
38
27
  phase() {
39
28
  return this.#phase;
40
29
  }
41
- /** Last activity timestamp - read by the Presenter's liveness heartbeat. */
42
30
  lastActivityMs() {
43
31
  return this.#lastActivityMs;
44
32
  }
45
- /** Set the activity baseline WITHOUT entering busy (sessionStart / revive). */
46
33
  resetActivity(ms) {
47
34
  this.#lastActivityMs = ms;
48
35
  }
49
- /**
50
- * Record agent activity. Idempotent while busy - only the first activity from idle/fading flips the
51
- * glow on (no strobe). `ms` lets log read the clock exactly once per row.
52
- */
53
36
  markActivity(ms = this.#now()) {
54
37
  this.#lastActivityMs = ms;
55
38
  if (this.#phase === GlowPhase.IDLE || this.#phase === GlowPhase.FADING)
56
39
  this.#enterBusy();
57
40
  this.#armIdleCheck();
58
41
  }
59
- /** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
60
42
  scheduleIdle() {
61
43
  this.#armIdleCheck();
62
44
  }
63
- /** Clear both timers (called from Presenter.destroy). */
64
45
  teardown() {
65
46
  if (this.#idleCheckTimer !== undefined)
66
47
  nativeClearTimeout(this.#idleCheckTimer);
@@ -76,14 +57,11 @@ export class GlowController {
76
57
  }
77
58
  this.#phase = GlowPhase.BUSY;
78
59
  if (this.#borderMode === BorderMode.SESSION) {
79
- // Session mode: modulate only the shimmer; the base border (data-on) stays session-owned.
80
60
  this.#glow?.setAttribute(DATA_BUSY, BUSY_ON);
81
61
  }
82
62
  else {
83
- // back-compat: the busy machine owns the base border.
84
63
  this.#glow?.setAttribute(DATA_ON, GLOW_ON);
85
64
  }
86
- // The HUD/log is session-owned (sessionStart/End), NOT toggled here - it stays put on idle.
87
65
  this.#cursor?.setAttribute(DATA_ON, GLOW_ON);
88
66
  }
89
67
  #armIdleCheck() {
@@ -97,7 +75,6 @@ export class GlowController {
97
75
  return;
98
76
  const quietFor = this.#now() - this.#lastActivityMs;
99
77
  if (quietFor < this.#idleAfterMs) {
100
- // Activity landed during the wait; re-arm for only the remaining quiet time.
101
78
  this.#idleCheckTimer = nativeSetTimeout(() => this.#checkIdle(), this.#idleAfterMs - quietFor);
102
79
  return;
103
80
  }
@@ -106,16 +83,13 @@ export class GlowController {
106
83
  #beginFade() {
107
84
  this.#phase = GlowPhase.FADING;
108
85
  if (this.#borderMode === BorderMode.SESSION) {
109
- // Session mode: relax the shimmer only; the base border stays on.
110
86
  this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
111
87
  }
112
88
  else {
113
- // back-compat: fade the base border out.
114
89
  this.#glow?.setAttribute(DATA_ON, GLOW_OFF);
115
90
  }
116
- // Do NOT hide the HUD/log on idle - it persists for the whole session (sessionStart/End).
117
91
  this.#cursor?.setAttribute(DATA_ON, GLOW_OFF);
118
- this.#setMode(PresenterMode.IDLE); // clear the READING/ACTING chip when going quiet
92
+ this.#setMode(PresenterMode.IDLE);
119
93
  this.#fadeTimer = nativeSetTimeout(() => {
120
94
  this.#fadeTimer = undefined;
121
95
  if (this.#phase === GlowPhase.FADING)
@@ -0,0 +1,18 @@
1
+ export declare function isHudDragged(hud: HTMLElement): boolean;
2
+ export declare function clampHudPosition(left: number, top: number, width: number, height: number, viewportWidth: number, viewportHeight: number, margin?: number): {
3
+ left: number;
4
+ top: number;
5
+ };
6
+ export declare function applyHudPosition(hud: HTMLElement, left: number, top: number): void;
7
+ export declare function resetHudDockPosition(hud: HTMLElement): void;
8
+ export declare function hudLayoutBox(hud: HTMLElement): {
9
+ left: number;
10
+ top: number;
11
+ width: number;
12
+ height: number;
13
+ };
14
+ export declare function readHudPosition(hud: HTMLElement): {
15
+ left: number;
16
+ top: number;
17
+ };
18
+ export declare function relayoutHudPosition(hud: HTMLElement): void;
@@ -0,0 +1,50 @@
1
+ import { HUD_DOCK_MARGIN_PX, HUD_DRAGGED_ATTR, HUD_POS_X_VAR, HUD_POS_Y_VAR, } from './presenter-config.js';
2
+ export function isHudDragged(hud) {
3
+ return '1' === hud.getAttribute(HUD_DRAGGED_ATTR);
4
+ }
5
+ export function clampHudPosition(left, top, width, height, viewportWidth, viewportHeight, margin = HUD_DOCK_MARGIN_PX) {
6
+ const minLeft = margin;
7
+ const minTop = margin;
8
+ const maxLeft = Math.max(minLeft, viewportWidth - width - margin);
9
+ const maxTop = Math.max(minTop, viewportHeight - height - margin);
10
+ return {
11
+ left: Math.max(minLeft, Math.min(left, maxLeft)),
12
+ top: Math.max(minTop, Math.min(top, maxTop)),
13
+ };
14
+ }
15
+ export function applyHudPosition(hud, left, top) {
16
+ hud.setAttribute(HUD_DRAGGED_ATTR, '1');
17
+ hud.style.setProperty(HUD_POS_X_VAR, `${String(left)}px`);
18
+ hud.style.setProperty(HUD_POS_Y_VAR, `${String(top)}px`);
19
+ }
20
+ export function resetHudDockPosition(hud) {
21
+ hud.removeAttribute(HUD_DRAGGED_ATTR);
22
+ hud.style.removeProperty(HUD_POS_X_VAR);
23
+ hud.style.removeProperty(HUD_POS_Y_VAR);
24
+ }
25
+ export function hudLayoutBox(hud) {
26
+ const rect = hud.getBoundingClientRect();
27
+ return {
28
+ left: rect.left,
29
+ top: rect.top,
30
+ width: rect.width,
31
+ height: rect.height,
32
+ };
33
+ }
34
+ export function readHudPosition(hud) {
35
+ const x = hud.style.getPropertyValue(HUD_POS_X_VAR);
36
+ const y = hud.style.getPropertyValue(HUD_POS_Y_VAR);
37
+ if (x !== '' && y !== '') {
38
+ return { left: Number.parseFloat(x), top: Number.parseFloat(y) };
39
+ }
40
+ const rect = hud.getBoundingClientRect();
41
+ return { left: rect.left, top: rect.top };
42
+ }
43
+ export function relayoutHudPosition(hud) {
44
+ if (!isHudDragged(hud))
45
+ return;
46
+ const { left, top } = readHudPosition(hud);
47
+ const { width, height } = hudLayoutBox(hud);
48
+ const next = clampHudPosition(left, top, width, height, window.innerWidth, window.innerHeight);
49
+ applyHudPosition(hud, next.left, next.top);
50
+ }
@@ -1,7 +1 @@
1
- /**
2
- * The impact report's surface.
3
- *
4
- * Same glass as the chat panel and anchored in the same slot above the toolbar, because it is the
5
- * same object seen a different way: what this session is doing, and what every session has done.
6
- */
7
1
  export declare const REPORT_CSS: string;
@@ -1,12 +1,6 @@
1
- import { HUD_DROP_SHADOW } from './presenter-hud-chrome.js';
1
+ import { HUD_DROP_SHADOW } from './chrome/presenter-hud-chrome.js';
2
2
  import { DOCK_ALIGN_ATTR, DOCK_ATTR, REPORT_ATTR, REPORT_PANEL_ATTR } from './presenter-config.js';
3
3
  const OVERLAY = 'data-reticle-overlay';
4
- /**
5
- * The impact report's surface.
6
- *
7
- * Same glass as the chat panel and anchored in the same slot above the toolbar, because it is the
8
- * same object seen a different way: what this session is doing, and what every session has done.
9
- */
10
4
  export const REPORT_CSS = `
11
5
  [${REPORT_PANEL_ATTR}]{
12
6
  position:absolute;right:0;left:auto;bottom:calc(100% + 12px);z-index:31;
@@ -88,6 +82,12 @@ export const REPORT_CSS = `
88
82
  [${REPORT_PANEL_ATTR}] .reticle-report-defect-source{
89
83
  color:var(--reticle-faint);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
90
84
  overflow-wrap:anywhere;}
85
+ .reticle-report-defect-link{flex:none;display:inline-flex;align-items:center;justify-content:center;
86
+ width:20px;height:20px;border-radius:6px;color:var(--reticle-faint);pointer-events:auto;
87
+ opacity:0;transition:opacity .12s ease,color .12s ease,background .12s ease;}
88
+ .reticle-report-defect:hover .reticle-report-defect-link,
89
+ .reticle-report-defect-link:focus-visible{opacity:1;}
90
+ .reticle-report-defect-link:hover{color:var(--reticle-fg);background:rgba(255,255,255,.07);}
91
91
  [${REPORT_PANEL_ATTR}] .reticle-report-defects-more{
92
92
  display:inline-block;margin-top:8px;font-size:10px;color:var(--reticle-c-active);
93
93
  text-decoration:none;}
@@ -123,4 +123,19 @@ export const REPORT_CSS = `
123
123
  [${REPORT_PANEL_ATTR}] .reticle-report-links a{
124
124
  color:var(--reticle-faint);font-size:10.5px;text-decoration:none;}
125
125
  [${REPORT_PANEL_ATTR}] .reticle-report-links a:hover{color:var(--reticle-fg);text-decoration:underline;}
126
+
127
+ /* Who this record belongs to, and the push-now control. Tokens only — no literal colours. */
128
+ [${REPORT_PANEL_ATTR}] .reticle-report-identity{
129
+ display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:10px 14px 0;min-height:0;}
130
+ [${REPORT_PANEL_ATTR}] .reticle-report-identity:empty{display:none;}
131
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now{
132
+ display:inline-flex;align-items:center;justify-content:center;
133
+ border:none;background:transparent;color:var(--reticle-faint);cursor:pointer;line-height:0;padding:2px;}
134
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now:hover{color:var(--reticle-fg);}
135
+ /* The one moment this control has an opinion: it says it is going, not that it has arrived. */
136
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now[data-syncing="1"]{
137
+ color:var(--reticle-c-active);animation:reticle-sync-spin 900ms linear infinite;}
138
+ @keyframes reticle-sync-spin{to{transform:rotate(360deg);}}
139
+ @media (prefers-reduced-motion:reduce){
140
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now[data-syncing="1"]{animation:none;}}
126
141
  `;
@@ -1,16 +1,14 @@
1
- import { type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
1
+ import { type AccountState, type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
2
2
  export declare function reportPanelHtml(): string;
3
- export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string): string;
4
- export interface ReportHost {
5
- /** Opened from the toolbar and from the chat, so the shell decides what else must close. */
3
+ export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState, projectName?: string): string;
4
+ interface ReportHost {
6
5
  onBeforeOpen?: () => void;
6
+ onSyncNow?: () => void;
7
7
  }
8
- /** The report panel controller: scope toggle, live repaint, share + referral actions. */
9
8
  export declare class PresenterReport {
10
9
  #private;
11
10
  constructor(host?: ReportHost);
12
11
  mount(root: HTMLElement): void;
13
- /** The record arrived from the daemon. Repaint only if the panel is on screen. */
14
12
  setSnapshot(snapshot: ImpactSnapshot): void;
15
13
  snapshot(): ImpactSnapshot | undefined;
16
14
  isOpen(): boolean;
@@ -18,6 +16,6 @@ export declare class PresenterReport {
18
16
  close(): void;
19
17
  toggle(): void;
20
18
  contains(node: Node): boolean;
21
- /** The post text for whichever scope is showing. */
22
19
  shareText(): string;
23
20
  }
21
+ export {};
@@ -1,14 +1,10 @@
1
- import { IMPACT_DEFECT_LIMIT } from '@reticlehq/core';
2
- import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './presenter-icons.js';
3
- import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
1
+ import { IMPACT_DEFECT_LIMIT, } from '@reticlehq/core';
2
+ import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './icons/presenter-icons.js';
3
+ import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
4
+ import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
5
+ import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, SYNC_BTN_ATTR, accountControlHtml, syncButtonHtml, } from './presenter-account.js';
4
6
  import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
5
- import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './presenter-report-copy.js';
6
- /**
7
- * The impact report: what Reticle has actually done for this user.
8
- *
9
- * Reads the local record only - the panel is a view over `.reticle/impact.json`, pushed by the
10
- * daemon, never fetched from us. Two scopes (this project, this machine) behind one toggle.
11
- */
7
+ import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './chrome/presenter-report-copy.js';
12
8
  const SCOPE_ATTR = 'data-reticle-report-scope';
13
9
  const SHARE_X_ATTR = 'data-reticle-share-x';
14
10
  const SHARE_IN_ATTR = 'data-reticle-share-in';
@@ -17,7 +13,7 @@ const REFER_ATTR = 'data-reticle-refer';
17
13
  const COPIED_FLASH_MS = 1600;
18
14
  export function reportPanelHtml() {
19
15
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
20
- return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="dialog" aria-label="Reticle impact" aria-hidden="true">
16
+ return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="region" aria-label="Reticle impact" aria-hidden="true">
21
17
  <div class="reticle-report-inner">
22
18
  <div class="reticle-report-head">
23
19
  <span class="reticle-report-title">${REPORT_TEXT.TITLE}</span>
@@ -40,19 +36,12 @@ export function reportPanelHtml() {
40
36
  </div>
41
37
  </div>`;
42
38
  }
43
- /** A stat card: the number, what it is, and - for an estimate - what it is measured against. */
44
39
  function card(value, label, basis) {
45
40
  const tag = basis === undefined
46
41
  ? ''
47
42
  : `<span class="reticle-report-basis" title="${basis}">${REPORT_TEXT.ESTIMATE_TAG}</span>`;
48
43
  return `<div class="reticle-report-card"><span class="reticle-report-value">${value}</span><span class="reticle-report-label">${label}${tag}</span></div>`;
49
44
  }
50
- /**
51
- * A 30-day bar chart of verdicts, as inline spans.
52
- *
53
- * Bars rather than a line: a day with no verdicts is a real fact about the week, and a line chart
54
- * interpolates straight through it.
55
- */
56
45
  function chart(scope) {
57
46
  const days = scope.days.slice(-30);
58
47
  if (0 === days.length)
@@ -68,61 +57,14 @@ function chart(scope) {
68
57
  .join('');
69
58
  return `<div class="reticle-report-chart-wrap"><span class="reticle-report-section">${REPORT_TEXT.CHART}</span><div class="reticle-report-chart">${bars}</div></div>`;
70
59
  }
71
- /**
72
- * Escape text that came from the app under test.
73
- *
74
- * A defect title is an element's accessible name or a verdict's failure reason — both of which are
75
- * ultimately the CONTENT of somebody else's page, and this panel builds its DOM from an HTML string.
76
- * Everything else the report renders is a number or a date; this is the first app-derived text to
77
- * reach it, so the escaping arrives with it.
78
- */
79
- function esc(value) {
80
- return value
81
- .replace(/&/g, '&amp;')
82
- .replace(/</g, '&lt;')
83
- .replace(/>/g, '&gt;')
84
- .replace(/"/g, '&quot;');
85
- }
86
- /**
87
- * The short list of what is currently broken.
88
- *
89
- * The hero number already says HOW MANY defects Reticle caught; this says WHICH ONES, which is the
90
- * difference between a statistic and something a person can act on. Deliberately short: a panel in
91
- * the corner of somebody's app is not a triage queue, and the link at the bottom is where the queue
92
- * lives. When the project is not linked to a workspace there is no link and no nagging — the free
93
- * tool is complete on its own.
94
- */
95
- /**
96
- * Whether a dashboard link is safe to render as a clickable href.
97
- *
98
- * The url arrives from `.reticle/cloud.json` — a file in somebody's repository, which means it is
99
- * INPUT. Escaping the quotes stops it breaking out of the attribute but does nothing about the
100
- * SCHEME, so `javascript:...` produced a link that ran code inside the developer's own application,
101
- * from a panel Reticle injected there. Only the two schemes a dashboard can actually live on are
102
- * allowed; anything else renders no link at all, which is the same state as an unlinked project and
103
- * therefore already a supported one.
104
- */
105
- function isSafeDashboardUrl(raw) {
106
- try {
107
- const scheme = new URL(raw).protocol;
108
- return 'https:' === scheme || 'http:' === scheme;
109
- }
110
- catch {
111
- // Not a url at all. A relative path cannot address a dashboard on another origin, so there is
112
- // nothing to render and nothing lost by refusing it.
113
- return false;
114
- }
115
- }
116
60
  function defects(scope, dashboardUrl) {
117
- /*
118
- * Defaulted here as well as in the schema. Zod's default applies when a record is PARSED, and the
119
- * snapshot reaching this panel is pushed straight from the daemon rather than round-tripped
120
- * through the schema — so a record written by an older build arrives with no `defects` field at
121
- * all and used to throw, taking the whole report down with it.
122
- */
123
61
  const list = (scope.defects ?? []).slice(0, IMPACT_DEFECT_LIMIT);
124
62
  if (0 === list.length)
125
63
  return '';
64
+ const linked = dashboardUrl !== undefined && isSafeDashboardUrl(dashboardUrl);
65
+ const rowLink = linked
66
+ ? `<a class="reticle-report-defect-link" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener" title="${REPORT_TEXT.DEFECT_LINK_TITLE}" aria-label="${REPORT_TEXT.DEFECT_LINK_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.SEND)}</a>`
67
+ : '';
126
68
  const rows = list
127
69
  .map((d) => {
128
70
  const detail = d.detail === undefined
@@ -131,17 +73,15 @@ function defects(scope, dashboardUrl) {
131
73
  const source = d.source === undefined
132
74
  ? ''
133
75
  : `<span class="reticle-report-defect-source">${esc(d.source)}</span>`;
134
- return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}</li>`;
76
+ return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}${rowLink}</li>`;
135
77
  })
136
78
  .join('');
137
- // Only claim there are more when there actually are — `counts.failed` is every defect ever, and
138
- // this list is the recent tail of it.
139
- const more = dashboardUrl === undefined || !isSafeDashboardUrl(dashboardUrl)
79
+ const more = !linked
140
80
  ? ''
141
81
  : `<a class="reticle-report-defects-more" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener">${REPORT_TEXT.DEFECTS_MORE}${scope.counts.failed > list.length ? ` (${String(scope.counts.failed)})` : ''}</a>`;
142
82
  return `<div class="reticle-report-defects-wrap"><span class="reticle-report-section">${REPORT_TEXT.DEFECTS}</span><ul class="reticle-report-defects">${rows}</ul>${more}</div>`;
143
83
  }
144
- export function reportBodyHtml(scope, dashboardUrl) {
84
+ export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
145
85
  const c = scope.counts;
146
86
  if (0 === c.calls)
147
87
  return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
@@ -164,27 +104,23 @@ export function reportBodyHtml(scope, dashboardUrl) {
164
104
  card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
165
105
  card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
166
106
  ].join('');
167
- return `${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl)}`;
107
+ const identity = `<div class="reticle-report-identity">${accountControlHtml(account, { dashboardUrl, projectName, verdicts: c.verdicts, defects: c.failed })}${syncButtonHtml(dashboardUrl)}</div>`;
108
+ return `${identity}${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
168
109
  }
169
- /**
170
- * The one line an UNLINKED user is shown about the dashboard.
171
- *
172
- * Absent `dashboardUrl` IS the unlinked signal — it is only ever set from a repo's cloud.json — so
173
- * this needs no new plumbing and cannot be wrong about the state it describes.
174
- *
175
- * Gated on a VERDICT, not on tool calls. Somebody who has driven the app but proved nothing has not
176
- * yet received the thing this offers to preserve, and offering to keep nothing is an advert. Past
177
- * that bar it is a fact about where their record lives, at the foot of a panel they opened on
178
- * purpose — which is why it does not need to be dismissible.
179
- */
180
- function localOnly(scope, dashboardUrl) {
110
+ function localOnly(scope, dashboardUrl, account) {
181
111
  if (dashboardUrl !== undefined)
182
112
  return '';
183
113
  if (scope.counts.verdicts <= 0)
184
114
  return '';
185
- return `<p class="reticle-report-local-only">${REPORT_TEXT.LOCAL_ONLY} <code>${REPORT_TEXT.LOCAL_ONLY_ACTION}</code> ${REPORT_TEXT.LOCAL_ONLY_TAIL}</p>`;
115
+ if (account === undefined)
116
+ return '';
117
+ const [lead, action, tail] = account.signedIn
118
+ ? [REPORT_TEXT.UNLINKED, REPORT_TEXT.UNLINKED_ACTION, REPORT_TEXT.UNLINKED_TAIL]
119
+ : [REPORT_TEXT.LOCAL_ONLY, REPORT_TEXT.LOCAL_ONLY_ACTION, REPORT_TEXT.LOCAL_ONLY_TAIL];
120
+ return `<p class="reticle-report-local-only">${lead} <code>${action}</code> ${tail}</p>`;
186
121
  }
187
- /** The report panel controller: scope toggle, live repaint, share + referral actions. */
122
+ const SYNC_SPIN_MS = 1_200;
123
+ const SYNCING_ATTR = 'data-syncing';
188
124
  export class PresenterReport {
189
125
  #panel;
190
126
  #body;
@@ -217,8 +153,6 @@ export class PresenterReport {
217
153
  });
218
154
  root.querySelector(`[${SHARE_IN_ATTR}]`)?.addEventListener('click', (e) => {
219
155
  e.stopPropagation();
220
- // LinkedIn ignores every text parameter, so the caption goes to the clipboard first and the
221
- // composer opens on the link - the only route that needs no posting permission.
222
156
  void this.#copy(this.shareText());
223
157
  this.#openShare(buildLinkedInShareUrl());
224
158
  });
@@ -230,8 +164,25 @@ export class PresenterReport {
230
164
  e.stopPropagation();
231
165
  void this.#copy(buildReferralText(), e.currentTarget);
232
166
  });
167
+ root.addEventListener('click', (e) => {
168
+ const target = e.target;
169
+ if (!(target instanceof Element))
170
+ return;
171
+ const signin = target.closest(`[${ACCOUNT_SIGNIN_ATTR}]`);
172
+ if (signin !== null) {
173
+ e.stopPropagation();
174
+ void this.#copy(ACCOUNT_TEXT.SIGNIN_COMMAND, signin);
175
+ return;
176
+ }
177
+ const sync = target.closest(`[${SYNC_BTN_ATTR}]`);
178
+ if (sync !== null) {
179
+ e.stopPropagation();
180
+ this.#host.onSyncNow?.();
181
+ sync.setAttribute(SYNCING_ATTR, '1');
182
+ setTimeout(() => sync.removeAttribute(SYNCING_ATTR), SYNC_SPIN_MS);
183
+ }
184
+ });
233
185
  }
234
- /** The record arrived from the daemon. Repaint only if the panel is on screen. */
235
186
  setSnapshot(snapshot) {
236
187
  this.#snapshot = snapshot;
237
188
  if (this.isOpen())
@@ -264,7 +215,6 @@ export class PresenterReport {
264
215
  contains(node) {
265
216
  return true === this.#panel?.contains(node);
266
217
  }
267
- /** The post text for whichever scope is showing. */
268
218
  shareText() {
269
219
  const snap = this.#snapshot;
270
220
  if (snap === undefined)
@@ -286,7 +236,7 @@ export class PresenterReport {
286
236
  this.#body.innerHTML =
287
237
  scope === undefined
288
238
  ? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
289
- : reportBodyHtml(scope, this.#snapshot?.dashboardUrl);
239
+ : reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName);
290
240
  }
291
241
  #openShare(url) {
292
242
  window.open(url, '_blank', 'noopener,noreferrer');
@@ -296,7 +246,6 @@ export class PresenterReport {
296
246
  await navigator.clipboard?.writeText(text);
297
247
  }
298
248
  catch {
299
- // A blocked clipboard is not worth an error dialog over; the text is on screen either way.
300
249
  }
301
250
  if (button instanceof HTMLElement) {
302
251
  const original = button.textContent ?? '';
@@ -1,7 +1,6 @@
1
1
  import { SessionState } from '@reticlehq/core';
2
- import { type LogKind, type LogResult } from './presenter-log.js';
2
+ import { type LogKind, type LogResult } from './chrome/presenter-log.js';
3
3
  import { type Capabilities } from '../registry/capabilities.js';
4
- /** The in-page run-state export (Copy/Export buttons). The full event ring-buffer is server-side. */
5
4
  export interface PresenterRunState {
6
5
  session: string;
7
6
  url: string;
@@ -24,7 +23,6 @@ export interface PresenterRunState {
24
23
  result?: LogResult;
25
24
  }[];
26
25
  }
27
- /** Everything the controller hands the builder - the live fields a run state is computed from. */
28
26
  interface RunStateInput {
29
27
  sessionId: string;
30
28
  state: SessionState;
@@ -38,10 +36,5 @@ interface RunStateInput {
38
36
  result?: LogResult;
39
37
  }[];
40
38
  }
41
- /**
42
- * Compute the exported run-state from the controller's live fields: per-kind + pass/fail counts over
43
- * the activity log, the session duration, and the current capability surface. Extracted from
44
- * presenter.ts so the controller stays under the size cap; the counting is the same as before.
45
- */
46
39
  export declare function buildRunState(input: RunStateInput): PresenterRunState;
47
40
  export {};