@reticlehq/browser 3.5.0 → 3.6.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 (87) hide show
  1. package/dist/actions/actions-dom.js +44 -43
  2. package/dist/actions/actions-press.d.ts +10 -4
  3. package/dist/actions/actions-press.js +108 -17
  4. package/dist/actions/actions.d.ts +2 -0
  5. package/dist/actions/actions.js +25 -16
  6. package/dist/actions/danger-context.js +1 -1
  7. package/dist/connect-options.d.ts +1 -0
  8. package/dist/dom/a11y.js +13 -4
  9. package/dist/dom/addressing/refs.js +2 -1
  10. package/dist/dom/addressing/testid-attr.d.ts +4 -0
  11. package/dist/dom/addressing/testid-attr.js +14 -0
  12. package/dist/dom/query.d.ts +1 -0
  13. package/dist/dom/query.js +38 -11
  14. package/dist/dom/snapshot.js +1 -0
  15. package/dist/observers/departure.js +51 -9
  16. package/dist/observers/dom.js +3 -2
  17. package/dist/observers/field.js +5 -3
  18. package/dist/observers/health.js +63 -2
  19. package/dist/observers/network-stream.d.ts +1 -1
  20. package/dist/observers/network-stream.js +3 -3
  21. package/dist/observers/network.js +1 -1
  22. package/dist/observers/storage.js +6 -1
  23. package/dist/presenter/carousel/carousel.d.ts +5 -2
  24. package/dist/presenter/carousel/carousel.js +34 -7
  25. package/dist/presenter/carousel/offer-card.d.ts +2 -2
  26. package/dist/presenter/carousel/offer-card.js +2 -1
  27. package/dist/presenter/carousel/panel-slides.d.ts +4 -3
  28. package/dist/presenter/carousel/panel-slides.js +43 -6
  29. package/dist/presenter/carousel/talk-card.d.ts +1 -1
  30. package/dist/presenter/chrome/log-kinds.d.ts +5 -0
  31. package/dist/presenter/chrome/log-kinds.js +1 -0
  32. package/dist/presenter/chrome/log-memory.d.ts +12 -0
  33. package/dist/presenter/chrome/log-memory.js +39 -0
  34. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
  35. package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
  36. package/dist/presenter/chrome/presenter-log.d.ts +5 -4
  37. package/dist/presenter/chrome/presenter-log.js +55 -5
  38. package/dist/presenter/chrome/presenter-report-copy.d.ts +11 -1
  39. package/dist/presenter/chrome/presenter-report-copy.js +14 -1
  40. package/dist/presenter/hud-telemetry.js +31 -2
  41. package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
  42. package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
  43. package/dist/presenter/icons/presenter-icons.d.ts +2 -0
  44. package/dist/presenter/icons/presenter-icons.js +2 -0
  45. package/dist/presenter/presenter-account.d.ts +5 -4
  46. package/dist/presenter/presenter-account.js +23 -10
  47. package/dist/presenter/presenter-chat-views.d.ts +21 -0
  48. package/dist/presenter/presenter-chat-views.js +382 -0
  49. package/dist/presenter/presenter-config.d.ts +3 -2
  50. package/dist/presenter/presenter-config.js +4 -3
  51. package/dist/presenter/presenter-controls.d.ts +4 -2
  52. package/dist/presenter/presenter-controls.js +231 -51
  53. package/dist/presenter/presenter-design-tokens.d.ts +1 -0
  54. package/dist/presenter/presenter-design-tokens.js +34 -0
  55. package/dist/presenter/presenter-dock-layout.js +1 -1
  56. package/dist/presenter/presenter-frame.d.ts +12 -0
  57. package/dist/presenter/presenter-frame.js +189 -0
  58. package/dist/presenter/presenter-plan.d.ts +10 -0
  59. package/dist/presenter/presenter-plan.js +185 -0
  60. package/dist/presenter/presenter-report-styles.js +7 -0
  61. package/dist/presenter/presenter-report.d.ts +1 -1
  62. package/dist/presenter/presenter-report.js +53 -16
  63. package/dist/presenter/presenter-settings-styles.js +10 -0
  64. package/dist/presenter/presenter-settings.d.ts +5 -0
  65. package/dist/presenter/presenter-settings.js +71 -11
  66. package/dist/presenter/presenter-shell-styles.js +39 -42
  67. package/dist/presenter/presenter-shell.d.ts +8 -1
  68. package/dist/presenter/presenter-shell.js +124 -72
  69. package/dist/presenter/presenter-styles.js +9 -1
  70. package/dist/presenter/presenter.d.ts +4 -3
  71. package/dist/presenter/presenter.js +101 -13
  72. package/dist/presenter/tour/tour.d.ts +4 -1
  73. package/dist/presenter/tour/tour.js +6 -4
  74. package/dist/recorder/recorder.js +2 -2
  75. package/dist/registry/auto-testids.js +3 -4
  76. package/dist/registry/declared-platform.js +2 -0
  77. package/dist/reticle-inject.js +880 -513
  78. package/dist/reticle.d.ts +10 -1
  79. package/dist/reticle.js +73 -9
  80. package/dist/review/annotator-styles.d.ts +1 -1
  81. package/dist/review/annotator.d.ts +7 -0
  82. package/dist/review/annotator.js +20 -2
  83. package/dist/review/mark-anchor.js +2 -2
  84. package/dist/session-continuity.js +2 -1
  85. package/dist/storage-keys.d.ts +14 -0
  86. package/dist/storage-keys.js +17 -0
  87. package/package.json +3 -2
@@ -3,34 +3,53 @@ import { PresenterReport, reportPanelHtml } from './presenter-report.js';
3
3
  import { paintToolbarAccount, TOOLBAR_ACCOUNT_ATTR } from './presenter-workspace.js';
4
4
  import { mountAccountControl } from './presenter-account.js';
5
5
  import { paintSettingsAccount } from './presenter-settings.js';
6
- 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, SETTINGS_ATTR, SETTINGS_BTN_ATTR, MINIMISED_STORAGE_KEY, } from './presenter-config.js';
6
+ import { CHAT_MIN_ATTR, CHAT_PILL_ATTR, REPORT_ATTR, ANNOTATE_BTN_ATTR, CHAT_ATTR, CHAT_TOGGLE_ATTR, CHAT_PANEL_ATTR, DOCK_ATTR, FAB_ATTR, MIN_ATTR, SETTINGS_ATTR, SETTINGS_BTN_ATTR, MINIMISED_STORAGE_KEY, } from './presenter-config.js';
7
7
  import { offerDismissed } from './carousel/offer-card.js';
8
8
  import { paintCarousel } from './carousel/carousel.js';
9
9
  import { panelSlides } from './carousel/panel-slides.js';
10
- import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './chrome/presenter-brand.js';
11
- import { DATA_RETICLE_LOG, settleLogAtLatest } from './chrome/presenter-log.js';
10
+ import { paintRail, RAIL_HTML, RAIL_PROMO_ATTR } from './presenter-frame.js';
11
+ import { FAB_TOGGLE_HTML, MARK_SVG } from './chrome/presenter-brand.js';
12
+ import { settleLogAtLatest } from './chrome/presenter-log.js';
12
13
  import { installHudDragHandles, installHudPositionGuards } from './presenter-drag.js';
13
14
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
14
15
  import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
15
16
  import { HUD_SURFACE_CLASS, HUD_LOG_WELL_CLASS } from './chrome/presenter-hud-chrome.js';
16
- import { CONTROLS_TOOLBAR_HTML } from './presenter-controls.js';
17
+ import { ChatViews, CHAT_VIEWS_NAV_HTML, ANNOTATIONS_HTML, FLOWS_PAGE_HTML, platformBase, } from './presenter-chat-views.js';
17
18
  import { PresenterSettingsPanel, settingsPanelHtml, } from './presenter-settings.js';
18
19
  const DRAG_HANDLE_CLASS = 'reticle-toolbar-drag';
19
20
  const TRANSITION_LOCK_MS = 120;
20
- const ANNOTATE_LABEL = 'Annotate';
21
21
  const CHAT_MIN_LABEL = 'Minimise chat';
22
22
  const CHAT_PILL_LABEL = 'Open agent chat';
23
- const REPORT_LABEL = 'Impact';
24
23
  const SETTINGS_LABEL = 'Settings';
24
+ const AGENT_LOG_TITLE = 'Reticle';
25
25
  const EXIT_LABEL = 'Exit';
26
+ function paintPromo(root, offer, notices = [], base) {
27
+ const target = root.querySelector(`[${RAIL_PROMO_ATTR}]`);
28
+ if (null === target)
29
+ return;
30
+ let dismissed = false;
31
+ try {
32
+ dismissed = offerDismissed(window.localStorage);
33
+ }
34
+ catch {
35
+ }
36
+ let storage;
37
+ try {
38
+ storage = window.sessionStorage;
39
+ }
40
+ catch {
41
+ storage = undefined;
42
+ }
43
+ paintCarousel(target, panelSlides(offer, dismissed, notices, base), storage);
44
+ }
26
45
  export class HudShell {
27
46
  #root;
28
47
  #dock;
29
48
  #fab;
30
49
  #chatPanel;
31
- #chatToggle;
32
50
  #collapseBtn;
33
51
  #settings;
52
+ #chatViews;
34
53
  #dragTeardown;
35
54
  #layoutTeardown;
36
55
  #transitionLock = false;
@@ -48,9 +67,44 @@ export class HudShell {
48
67
  });
49
68
  #callbacks;
50
69
  #pushedAccount;
70
+ #pushedOffer;
71
+ #pushedNotices = [];
72
+ #pushedHarness;
73
+ #promoBase;
51
74
  #accountTeardown;
52
75
  paintHarness(config) {
76
+ this.#pushedHarness = config;
53
77
  this.#settings.paintHarness(config);
78
+ this.#chatViews.paintHarness(config);
79
+ this.#repaintPromoIfMoved();
80
+ }
81
+ #promoLinks() {
82
+ return platformBase(this.#pushedHarness, this.#pushedAccount?.account ?? { signedIn: false });
83
+ }
84
+ #repaintPromoIfMoved() {
85
+ const base = this.#promoLinks();
86
+ if (this.#root === undefined || base === this.#promoBase)
87
+ return;
88
+ this.#promoBase = base;
89
+ paintPromo(this.#root, this.#pushedOffer, this.#pushedNotices, base);
90
+ }
91
+ paintImpact(verdicts) {
92
+ this.#chatViews.paintImpact(verdicts);
93
+ }
94
+ paintAnnotations(items) {
95
+ this.#chatViews.paintAnnotations(items);
96
+ }
97
+ openView(view) {
98
+ if ('activity' === view)
99
+ this.showChat();
100
+ else {
101
+ if (this.isCollapsed())
102
+ this.expand();
103
+ this.closeChat();
104
+ this.#settings.close();
105
+ this.#report.close();
106
+ }
107
+ this.#chatViews.open(view);
54
108
  }
55
109
  paintAccount(account, dashboardUrl, details = {}) {
56
110
  this.#pushedAccount = { account, dashboardUrl, details };
@@ -58,65 +112,66 @@ export class HudShell {
58
112
  return;
59
113
  paintToolbarAccount(this.#root, account, dashboardUrl, details);
60
114
  paintSettingsAccount(this.#root, account, dashboardUrl, details);
115
+ paintRail(this.#root, account);
116
+ this.#chatViews.paintAccount(account);
117
+ this.#repaintPromoIfMoved();
61
118
  }
62
- #pushedOffer;
63
- paintOffer(offer) {
119
+ paintOffer(offer, notices = []) {
64
120
  this.#pushedOffer = offer;
65
- this.#paintCarousel();
66
- }
67
- #paintCarousel() {
68
- const log = this.#root?.querySelector(`[${DATA_RETICLE_LOG}]`);
69
- if (!(log instanceof HTMLElement))
70
- return;
71
- const declined = offerDismissed(this.#storage());
72
- paintCarousel(log, panelSlides(this.#pushedOffer, declined), this.#sessionStorage());
73
- }
74
- #sessionStorage() {
75
- try {
76
- return globalThis.sessionStorage;
77
- }
78
- catch {
79
- return undefined;
80
- }
81
- }
82
- #storage() {
83
- try {
84
- return globalThis.localStorage;
85
- }
86
- catch {
87
- return undefined;
88
- }
121
+ this.#pushedNotices = notices;
122
+ this.#promoBase = this.#promoLinks();
123
+ if (this.#root !== undefined)
124
+ paintPromo(this.#root, offer, notices, this.#promoBase);
89
125
  }
90
126
  constructor(callbacks = {}) {
91
127
  this.#callbacks = callbacks;
128
+ this.#chatViews = new ChatViews((enabled) => callbacks.onHarness?.(enabled), () => {
129
+ this.#chatViews.closePage();
130
+ this.closeChat();
131
+ this.#report.open();
132
+ }, (view) => {
133
+ if ('activity' === view) {
134
+ this.#chatViews.open(view);
135
+ this.showChat();
136
+ return;
137
+ }
138
+ if (this.isCollapsed())
139
+ this.expand();
140
+ this.closeChat();
141
+ this.#settings.close();
142
+ this.#report.close();
143
+ });
92
144
  this.#settings = new PresenterSettingsPanel({
93
145
  ...callbacks.settings,
94
146
  onBeforeOpen: () => {
95
147
  this.closeChat();
148
+ this.#chatViews.closePage();
96
149
  this.#report.close();
97
150
  callbacks.settings?.onBeforeOpen?.();
98
151
  },
99
152
  });
100
153
  }
101
154
  static dockHtml(actStripHtml, bannerHtml, logAttr, flowsHtml, footHtml) {
102
- const annotate = hiToggleIconHtml(PresenterIcon.ANNOTATE, PRESENTER_ICON_SIZE.TOOLBAR);
103
- const chart = hiToggleIconHtml(PresenterIcon.CHART, PRESENTER_ICON_SIZE.TOOLBAR);
104
155
  const gear = hiToggleIconHtml(PresenterIcon.GEAR, PRESENTER_ICON_SIZE.TOOLBAR);
105
156
  const exit = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.TOOLBAR);
106
- return `<div ${DOCK_ATTR}>
107
- <div ${CHAT_PANEL_ATTR} class="reticle-chat-panel ${HUD_SURFACE_CLASS}" role="region" aria-label="Reticle session" aria-hidden="true">
157
+ return `<div ${DOCK_ATTR} role="complementary" aria-label="Reticle">
158
+ <div ${CHAT_PANEL_ATTR} class="reticle-chat-panel ${HUD_SURFACE_CLASS}" role="region" aria-label="Agent Log" aria-hidden="true">
108
159
  <div class="reticle-chat-head">
109
- <span class="reticle-chat-brand">${MARK_SVG}<span class="reticle-chat-brandname">${BRAND_NAME}</span></span>
160
+ <span class="reticle-chat-brand">${MARK_SVG}<span class="reticle-chat-brandname">${AGENT_LOG_TITLE}</span></span>
110
161
  <span ${TOOLBAR_ACCOUNT_ATTR} class="reticle-head-account"></span>
111
162
  </div>
112
163
  <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>
113
164
  ${actStripHtml}
114
165
  <span class="reticle-tally" data-reticle-tally hidden></span>
115
- ${bannerHtml}
116
- <div class="${HUD_LOG_WELL_CLASS}"><div ${logAttr}></div></div>
117
- ${flowsHtml}
166
+ <section class="reticle-chat-view" role="region" aria-label="Agent Log">
167
+ ${bannerHtml}
168
+ <div class="${HUD_LOG_WELL_CLASS}"><div ${logAttr}></div></div>
169
+ ${flowsHtml}
170
+ </section>
118
171
  ${footHtml}
119
172
  </div>
173
+ ${FLOWS_PAGE_HTML}
174
+ ${ANNOTATIONS_HTML}
120
175
  <button type="button" ${CHAT_PILL_ATTR} class="reticle-chat-pill" title="${CHAT_PILL_LABEL}" aria-label="${CHAT_PILL_LABEL}">
121
176
  ${MARK_SVG}
122
177
  <span class="reticle-chat-pill-text" data-reticle-chat-pill-text></span>
@@ -125,21 +180,13 @@ export class HudShell {
125
180
  </button>
126
181
  ${settingsPanelHtml()}
127
182
  ${reportPanelHtml()}
183
+ ${RAIL_HTML}
128
184
  <div data-reticle-hud>
129
185
  <div class="reticle-hud-deco" aria-hidden="true"></div>
130
186
  ${FAB_TOGGLE_HTML}
131
187
  <div class="reticle-toolbar ${DRAG_HANDLE_CLASS}" role="toolbar" aria-label="Reticle controls">
132
- <div class="reticle-toolbar-actions">${CONTROLS_TOOLBAR_HTML}</div>
133
- <span class="reticle-tb-sep" aria-hidden="true"></span>
134
188
  <div class="reticle-toolbar-chrome">
135
- <div class="reticle-tb-wrap">
136
- <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>
137
- <span class="reticle-tb-tip">${ANNOTATE_LABEL}</span>
138
- </div>
139
- <div class="reticle-tb-wrap">
140
- <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>
141
- <span class="reticle-tb-tip">${REPORT_LABEL}</span>
142
- </div>
189
+ ${CHAT_VIEWS_NAV_HTML}
143
190
  <div class="reticle-tb-wrap">
144
191
  <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>
145
192
  <span class="reticle-tb-tip">${SETTINGS_LABEL}</span>
@@ -167,7 +214,15 @@ export class HudShell {
167
214
  };
168
215
  lit(CHAT_TOGGLE_ATTR, '1' === root.getAttribute(CHAT_ATTR));
169
216
  lit(SETTINGS_BTN_ATTR, '1' === root.getAttribute(SETTINGS_ATTR));
170
- lit(REPORT_BTN_ATTR, '1' === root.getAttribute(REPORT_ATTR));
217
+ const impact = root.querySelector('[data-reticle-chat-impact]');
218
+ if (impact !== null) {
219
+ const on = '1' === root.getAttribute(REPORT_ATTR);
220
+ impact.setAttribute('data-active', on ? '1' : '0');
221
+ if (on)
222
+ impact.setAttribute('aria-current', 'page');
223
+ else
224
+ impact.removeAttribute('aria-current');
225
+ }
171
226
  }
172
227
  isAnnotateOn() {
173
228
  return this.#annotateOn;
@@ -176,6 +231,14 @@ export class HudShell {
176
231
  this.#annotateOn = on;
177
232
  this.#annotateBtn?.setAttribute('aria-pressed', on ? 'true' : 'false');
178
233
  this.#annotateBtn?.setAttribute('data-active', on ? '1' : '0');
234
+ this.#annotateBtn?.setAttribute('aria-label', on ? 'Stop adding notes' : 'Add notes');
235
+ this.#annotateBtn?.setAttribute('title', on ? 'Stop adding notes' : 'Add notes');
236
+ const label = this.#annotateBtn?.querySelector('[data-reticle-notes-toggle-label]');
237
+ if (label !== null && label !== undefined)
238
+ label.textContent = on ? 'Stop adding notes' : 'Add notes';
239
+ this.#root
240
+ ?.querySelector('[data-reticle-chat-view-btn="annotations"]')
241
+ ?.setAttribute('data-capture', on ? '1' : '0');
179
242
  }
180
243
  mount(root) {
181
244
  this.#listeners = new AbortController();
@@ -186,10 +249,9 @@ export class HudShell {
186
249
  this.#fab = fabEl instanceof HTMLButtonElement ? fabEl : undefined;
187
250
  const chatPanelEl = root.querySelector(`[${CHAT_PANEL_ATTR}]`);
188
251
  this.#chatPanel = chatPanelEl instanceof HTMLElement ? chatPanelEl : undefined;
189
- const chatToggleEl = root.querySelector(`[${CHAT_TOGGLE_ATTR}]`);
190
- this.#chatToggle = chatToggleEl instanceof HTMLElement ? chatToggleEl : undefined;
191
252
  const annotateEl = root.querySelector(`[${ANNOTATE_BTN_ATTR}]`);
192
253
  this.#annotateBtn = annotateEl instanceof HTMLButtonElement ? annotateEl : undefined;
254
+ this.setAnnotateOn(this.#annotateOn);
193
255
  this.#annotateBtn?.addEventListener('click', (e) => {
194
256
  e.preventDefault();
195
257
  e.stopPropagation();
@@ -215,17 +277,15 @@ export class HudShell {
215
277
  root.removeAttribute(CHAT_ATTR);
216
278
  this.#settings.mount(root);
217
279
  this.#report.mount(root);
280
+ this.#chatViews.mount(root);
281
+ this.#promoBase = this.#promoLinks();
282
+ paintPromo(root, this.#pushedOffer, this.#pushedNotices, this.#promoBase);
218
283
  this.#toggleSync = new MutationObserver(() => this.#syncToolbarToggles());
219
284
  this.#toggleSync.observe(root, {
220
285
  attributes: true,
221
286
  attributeFilter: [CHAT_ATTR, SETTINGS_ATTR, REPORT_ATTR],
222
287
  });
223
288
  this.#syncToolbarToggles();
224
- root.querySelector(`[${REPORT_BTN_ATTR}]`)?.addEventListener('click', (e) => {
225
- e.preventDefault();
226
- e.stopPropagation();
227
- this.#report.toggle();
228
- }, { signal });
229
289
  this.#fab?.addEventListener('click', (e) => {
230
290
  e.stopPropagation();
231
291
  if (this.#suppressFabClick) {
@@ -238,10 +298,6 @@ export class HudShell {
238
298
  e.stopPropagation();
239
299
  this.collapse();
240
300
  }, { signal });
241
- this.#chatToggle?.addEventListener('click', (e) => {
242
- e.stopPropagation();
243
- this.toggleChat();
244
- }, { signal });
245
301
  const toolbarDrag = root.querySelector(`.${DRAG_HANDLE_CLASS}`);
246
302
  const dragHandles = [this.#fab, toolbarDrag].filter((el) => el instanceof HTMLElement);
247
303
  if (this.#dock !== undefined && dragHandles.length > 0) {
@@ -250,20 +306,18 @@ export class HudShell {
250
306
  this.#suppressFabClick = true;
251
307
  },
252
308
  onDragEnd: (moved) => {
253
- if (moved && this.isCollapsed())
254
- this.#suppressFabClick = true;
309
+ this.#suppressFabClick = moved && this.isCollapsed();
255
310
  },
256
311
  });
257
312
  this.#layoutTeardown = installHudPositionGuards(this.#dock, root);
258
313
  }
259
314
  document.addEventListener('pointerdown', this.#onDocPointerDown, { signal });
260
315
  document.addEventListener('keydown', this.#onKeyDown, { signal });
261
- this.#accountTeardown = mountAccountControl(root);
316
+ this.#accountTeardown = mountAccountControl(root, () => this.#callbacks.onSignIn?.());
262
317
  if (this.#pushedAccount !== undefined) {
263
318
  const pushed = this.#pushedAccount;
264
319
  this.paintAccount(pushed.account, pushed.dashboardUrl, pushed.details);
265
320
  }
266
- this.#paintCarousel();
267
321
  }
268
322
  teardown() {
269
323
  this.#accountTeardown?.();
@@ -277,11 +331,11 @@ export class HudShell {
277
331
  this.#layoutTeardown?.();
278
332
  this.#layoutTeardown = undefined;
279
333
  this.#settings.teardown();
334
+ this.#chatViews.teardown();
280
335
  this.#root = undefined;
281
336
  this.#dock = undefined;
282
337
  this.#fab = undefined;
283
338
  this.#chatPanel = undefined;
284
- this.#chatToggle = undefined;
285
339
  this.#collapseBtn = undefined;
286
340
  }
287
341
  isCollapsed() {
@@ -323,6 +377,8 @@ export class HudShell {
323
377
  if (this.isCollapsed())
324
378
  this.expand();
325
379
  this.#settings.close();
380
+ this.#chatViews.closePage();
381
+ this.#chatViews.open('activity');
326
382
  if (this.isChatOpen())
327
383
  return;
328
384
  this.#root.setAttribute(CHAT_ATTR, '1');
@@ -330,8 +386,6 @@ export class HudShell {
330
386
  if (log !== null)
331
387
  settleLogAtLatest(log);
332
388
  this.#chatPanel?.setAttribute('aria-hidden', 'false');
333
- this.#chatToggle?.setAttribute('data-active', '1');
334
- this.#chatToggle?.setAttribute('aria-pressed', 'true');
335
389
  this.#callbacks.onChatOpen?.();
336
390
  if (this.#dock !== undefined)
337
391
  scheduleSyncDockLayout(this.#dock, this.#root);
@@ -341,8 +395,6 @@ export class HudShell {
341
395
  return;
342
396
  this.#root.removeAttribute(CHAT_ATTR);
343
397
  this.#chatPanel?.setAttribute('aria-hidden', 'true');
344
- this.#chatToggle?.setAttribute('data-active', '0');
345
- this.#chatToggle?.setAttribute('aria-pressed', 'false');
346
398
  this.#callbacks.onChatClose?.();
347
399
  if (this.#dock !== undefined)
348
400
  scheduleSyncDockLayout(this.#dock, this.#root);
@@ -9,8 +9,13 @@ import { REPORT_CSS } from './presenter-report-styles.js';
9
9
  import { OFFER_CSS } from './carousel/offer-card.js';
10
10
  import { TALK_CSS } from './carousel/talk-card.js';
11
11
  import { CAROUSEL_CSS } from './carousel/carousel.js';
12
+ import { CHAT_VIEWS_CSS } from './presenter-chat-views.js';
13
+ import { PLAN_CSS } from './presenter-plan.js';
14
+ import { FRAME_CSS } from './presenter-frame.js';
15
+ import { PRESENTER_DESIGN_TOKENS_CSS } from './presenter-design-tokens.js';
12
16
  export { Z_OVERLAY };
13
17
  export const PRESENTER_CSS = `
18
+ ${PRESENTER_DESIGN_TOKENS_CSS}
14
19
  [data-reticle-overlay]{position:fixed;inset:0;pointer-events:none;z-index:${String(Z_OVERLAY)};}
15
20
  /**
16
21
  * The session glow: the page itself says an agent is here, and BREATHES while it is working.
@@ -70,4 +75,7 @@ ${OFFER_CSS}
70
75
  ${TALK_CSS}
71
76
  ${CAROUSEL_CSS}
72
77
  ${LOG_CSS}
73
- ${CONTROLS_CSS}`;
78
+ ${CHAT_VIEWS_CSS}
79
+ ${PLAN_CSS}
80
+ ${CONTROLS_CSS}
81
+ ${FRAME_CSS}`;
@@ -1,5 +1,5 @@
1
- import { PresenterMode, SessionState, type PresenterTone } from '@reticlehq/core';
2
- import { type LogKind, type LogResult, type LogHandle } from './chrome/presenter-log.js';
1
+ import { HudCorner, HudVisibility, PresenterMode, SessionState, type PresenterTone } from '@reticlehq/core';
2
+ import { type LogActor, type LogKind, type LogResult, type LogHandle } from './chrome/presenter-log.js';
3
3
  import { type PresenterOptions } from './presenter-config.js';
4
4
  import { type PresenterRunState } from './presenter-run-state.js';
5
5
  import { type ControlHandler } from './presenter-controls.js';
@@ -35,9 +35,10 @@ export declare class Presenter {
35
35
  get mode(): PresenterMode;
36
36
  setMode(mode: PresenterMode): void;
37
37
  status(text: string): void;
38
+ placeHud(visibility: HudVisibility | undefined, corner: HudCorner | undefined): string[];
38
39
  setIdleEndMs(ms: number): void;
39
40
  runState(): PresenterRunState | Record<string, unknown>;
40
- log(kind: LogKind, text: string, result?: LogResult): LogHandle | undefined;
41
+ log(kind: LogKind, text: string, result?: LogResult, actor?: LogActor): LogHandle | undefined;
41
42
  narrate(text: string, level?: string): LogHandle | undefined;
42
43
  setThrottled(throttled: boolean): void;
43
44
  beforeAct(refId: string, action: string, label: string): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { ActionType, HumanControlKind, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
1
+ import { ActionType, HudCorner, HudVisibility, HumanControlKind, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
2
2
  import { refs } from '../dom/addressing/refs.js';
3
3
  import { unreachableStripText } from '../transport/unreachable-message.js';
4
4
  import { actionVerb } from './chrome/presenter-verbs.js';
@@ -7,17 +7,21 @@ import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, hum
7
7
  import { PRESENTER_CSS } from './presenter-styles.js';
8
8
  import { HudShell } from './presenter-shell.js';
9
9
  import { parseImpactSnapshot } from './chrome/presenter-report-copy.js';
10
- import { BorderMode, DEFAULT_BORDER_MODE, DATA_BUSY, BUSY_OFF, effectivePaceMs, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, ACT_STRIP, STATE_ATTR, UNREACHABLE_STATE, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, THROTTLED_ATTR, LIVENESS_ATTR, MARKERS_BTN_ATTR, CLEAR_MARKS_ATTR, COPY_MARKS_ATTR, MARKS_ROW_ATTR, MARK_COUNT_ATTR, } from './presenter-config.js';
10
+ import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
11
+ import { applyHudPosition, clampHudPosition, hudLayoutBox, resetHudDockPosition, } from './presenter-hud-position.js';
12
+ import { BorderMode, DEFAULT_BORDER_MODE, DOCK_ATTR, HIDDEN_UNTIL_RESTART_ATTR, DATA_BUSY, BUSY_OFF, effectivePaceMs, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, ACT_STRIP, STATE_ATTR, UNREACHABLE_STATE, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, THROTTLED_ATTR, LIVENESS_ATTR, MARKERS_BTN_ATTR, CLEAR_MARKS_ATTR, COPY_MARKS_ATTR, MARKS_ROW_ATTR, MARK_COUNT_ATTR, } from './presenter-config.js';
11
13
  import { buildRunState } from './presenter-run-state.js';
12
14
  import { moveCursor, ringAround, spawnRipple, pace } from './chrome/presenter-effects.js';
13
15
  import { GlowController } from './presenter-glow.js';
14
16
  import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from './icons/presenter-icons.js';
15
17
  import { renderTally } from './chrome/presenter-tally.js';
16
- import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_MARKS_HTML, CONTROLS_FOOT_HTML, ENDED_FADE_MS, ControlPanel, } from './presenter-controls.js';
18
+ import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_MARKS_HTML, CONTROLS_FOOT_HTML, CONTROLS_STATUS_HTML, ENDED_FADE_MS, ControlPanel, } from './presenter-controls.js';
17
19
  import { statusTheme, blockerHtml, getPresenterSettings, OutputDetail, syncPageBlocker, } from './presenter-settings.js';
18
20
  import { Annotator } from '../review/annotator.js';
19
21
  import { shouldAutoOpenChat } from './presenter-shell.js';
20
22
  import { installHudTelemetry } from './hud-telemetry.js';
23
+ import { PLAN_HTML, PlanBoard, parsePlanView } from './presenter-plan.js';
24
+ import { claimLog, readRememberedLog, rememberLog, } from './chrome/log-memory.js';
21
25
  const COPIED_FLASH_MS = 1600;
22
26
  export { GlowPhase } from './presenter-config.js';
23
27
  export { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
@@ -51,6 +55,8 @@ export class Presenter {
51
55
  #sessionActive = false;
52
56
  #logMax;
53
57
  #log;
58
+ #plan = new PlanBoard();
59
+ #remembered = [];
54
60
  #logBaseMs;
55
61
  #onControl;
56
62
  #onHudUse;
@@ -92,12 +98,15 @@ export class Presenter {
92
98
  onAnnotateToggle: () => this.#syncAnnotator(),
93
99
  onCollapse: () => this.#syncAnnotator(),
94
100
  onSyncNow: () => this.#onControl?.({ kind: HumanControlKind.SYNC }),
101
+ onSignIn: () => this.#onControl?.({ kind: HumanControlKind.SIGNIN }),
102
+ onHarness: (enabled) => this.#onControl?.({ kind: HumanControlKind.HARNESS, text: enabled ? 'on' : 'off' }),
95
103
  settings: {
96
104
  onBeforeOpen: () => {
97
105
  if (this.#shell.isCollapsed())
98
106
  this.#shell.expand();
99
107
  },
100
108
  onHideUntilRestart: () => this.#applyHideUntilRestart(),
109
+ onKill: () => options.onKill?.(),
101
110
  onSettingsChange: (s) => this.#onSettingsChange(s),
102
111
  onHarness: (enabled) => this.#onControl?.({ kind: HumanControlKind.HARNESS, text: enabled ? 'on' : 'off' }),
103
112
  },
@@ -122,12 +131,24 @@ export class Presenter {
122
131
  const a = command.args;
123
132
  if (command.name === ReticleCommand.FLOWS)
124
133
  return void this.#panel.setFlows(a['flows']);
134
+ if (command.name === ReticleCommand.FLOW_PROGRESS)
135
+ return void this.#panel.setFlowProgress(a);
136
+ if (command.name === ReticleCommand.PLAN) {
137
+ const view = parsePlanView(a);
138
+ if (view !== undefined)
139
+ this.#plan.paint(view);
140
+ return;
141
+ }
125
142
  if (command.name === ReticleCommand.IMPACT) {
126
143
  const snapshot = parseImpactSnapshot(a['snapshot']);
127
144
  if (snapshot !== undefined) {
128
145
  this.#shell.report.setSnapshot(snapshot);
129
- this.#shell.paintOffer(snapshot.harnessOffer);
146
+ this.#shell.paintOffer(snapshot.harnessOffer, snapshot.notices);
130
147
  this.#shell.paintHarness(snapshot.harnessConfig);
148
+ this.#shell.paintImpact(snapshot.project.counts.verdicts);
149
+ const owner = true === snapshot.account?.signedIn ? snapshot.account.email : undefined;
150
+ if (claimLog(owner))
151
+ this.#clearRunLog();
131
152
  this.#shell.paintAccount(snapshot.account, snapshot.dashboardUrl, {
132
153
  projectName: snapshot.projectName,
133
154
  dashboardUrl: snapshot.dashboardUrl,
@@ -159,13 +180,13 @@ export class Presenter {
159
180
  document.head.appendChild(style);
160
181
  const root = document.createElement('div');
161
182
  root.setAttribute('data-reticle-overlay', '');
162
- const actStrip = `<div class="reticle-act-strip" data-liveness="idle"><span class="reticle-act-dot" aria-hidden="true"></span><span class="reticle-act">${ACT_STRIP.READY}</span><span class="reticle-chip" data-reticle-chip></span></div>`;
183
+ const actStrip = `<div class="reticle-act-strip" data-liveness="idle"><span class="reticle-act-dot" aria-hidden="true"></span><span class="reticle-act">${ACT_STRIP.READY}</span>${CONTROLS_BANNER_HTML}<span class="reticle-chip" data-reticle-chip></span><div class="reticle-act-actions">${CONTROLS_STATUS_HTML}</div></div>`;
163
184
  root.innerHTML = `
164
185
  ${blockerHtml()}
165
186
  <div data-reticle-glow></div>
166
187
  <div data-reticle-cursor></div>
167
188
  <div data-reticle-ring></div>
168
- ${HudShell.dockHtml(actStrip, CONTROLS_BANNER_HTML, DATA_RETICLE_LOG, CONTROLS_MARKS_HTML + CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML)}`;
189
+ ${HudShell.dockHtml(actStrip, PLAN_HTML, DATA_RETICLE_LOG, CONTROLS_MARKS_HTML + CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML)}`;
169
190
  document.body.appendChild(root);
170
191
  this.#root = root;
171
192
  this.#glow = root.querySelector('[data-reticle-glow]') ?? undefined;
@@ -185,12 +206,32 @@ export class Presenter {
185
206
  syncPageBlocker(root, getPresenterSettings(), false);
186
207
  this.#glowCtl.setElements(this.#glow, this.#cursor);
187
208
  this.#panel.mount(root, this.#glow);
209
+ this.#plan.mount(root);
188
210
  if (this.#onHudUse !== undefined) {
189
211
  this.#hudTelemetryTeardown = installHudTelemetry(document, root, this.#onHudUse);
190
212
  }
191
213
  this.setMode(this.#mode);
214
+ this.#restoreLog();
192
215
  this.#renderTally();
193
216
  }
217
+ #restoreLog() {
218
+ const log = this.#log;
219
+ if (log === undefined || 0 < this.#remembered.length)
220
+ return;
221
+ for (const row of readRememberedLog().slice(-this.#logMax)) {
222
+ const handle = appendLogRow(log, row.kind, row.text, row.ts, this.#logMax, row.actor);
223
+ if (row.result !== undefined)
224
+ handle.result(row.result);
225
+ this.#logBaseMs = this.#now() - row.at;
226
+ this.#runLog.push({
227
+ at: row.at,
228
+ kind: row.kind,
229
+ text: row.text,
230
+ ...(row.result === undefined ? {} : { result: row.result }),
231
+ });
232
+ this.#remembered.push(row);
233
+ }
234
+ }
194
235
  bindAnnotator(annotator) {
195
236
  this.#annotator = annotator;
196
237
  const root = this.#root;
@@ -207,9 +248,10 @@ export class Presenter {
207
248
  let marks = annotator.markCount;
208
249
  chrome.onCount = (n) => {
209
250
  if (0 === marks && n > 0)
210
- this.#shell.openChat();
251
+ this.#shell.openView('annotations');
211
252
  marks = n;
212
253
  };
254
+ chrome.onMarksChange = (items) => this.#shell.paintAnnotations(items);
213
255
  if (copy instanceof HTMLElement) {
214
256
  copy.addEventListener('click', (e) => {
215
257
  e.stopPropagation();
@@ -253,6 +295,8 @@ export class Presenter {
253
295
  this.#sessionActive = false;
254
296
  this.#logBaseMs = undefined;
255
297
  this.#log = undefined;
298
+ this.#remembered = [];
299
+ rememberLog([]);
256
300
  this.#root?.remove();
257
301
  document.querySelectorAll('style[data-reticle-overlay]').forEach((s) => s.remove());
258
302
  this.#root = undefined;
@@ -414,6 +458,37 @@ export class Presenter {
414
458
  this.#heartbeatTimer = undefined;
415
459
  }
416
460
  }
461
+ placeHud(visibility, corner) {
462
+ const root = this.#root;
463
+ if (root === undefined)
464
+ return [];
465
+ const applied = [];
466
+ if (HudVisibility.REMOVED === visibility) {
467
+ this.destroy();
468
+ return [visibility];
469
+ }
470
+ if (HudVisibility.HIDDEN === visibility)
471
+ root.setAttribute(HIDDEN_UNTIL_RESTART_ATTR, '1');
472
+ if (HudVisibility.SHOWN === visibility)
473
+ root.removeAttribute(HIDDEN_UNTIL_RESTART_ATTR);
474
+ if (visibility !== undefined)
475
+ applied.push(visibility);
476
+ const dock = root.querySelector(`[${DOCK_ATTR}]`);
477
+ if (corner !== undefined && dock instanceof HTMLElement) {
478
+ if (HudCorner.BOTTOM_RIGHT === corner)
479
+ resetHudDockPosition(dock);
480
+ else {
481
+ const box = hudLayoutBox(dock);
482
+ const left = corner.endsWith('left') ? 0 : window.innerWidth;
483
+ const top = corner.startsWith('top') ? 0 : window.innerHeight;
484
+ const at = clampHudPosition(left, top, box.width, box.height, window.innerWidth, window.innerHeight);
485
+ applyHudPosition(dock, at.left, at.top);
486
+ }
487
+ applied.push(corner);
488
+ scheduleSyncDockLayout(dock, root);
489
+ }
490
+ return applied;
491
+ }
417
492
  setIdleEndMs(ms) {
418
493
  if (!Number.isFinite(ms))
419
494
  return;
@@ -446,6 +521,8 @@ export class Presenter {
446
521
  }
447
522
  #clearRunLog() {
448
523
  this.#runLog = [];
524
+ this.#remembered = [];
525
+ rememberLog([]);
449
526
  this.#tallied = { passes: 0, fails: 0 };
450
527
  if (this.#log !== undefined)
451
528
  clearLogRows(this.#log);
@@ -463,12 +540,9 @@ export class Presenter {
463
540
  else {
464
541
  this.#renderTally();
465
542
  }
466
- if (this.#root !== undefined) {
467
- const live = SessionState.ACTIVE === this.#panel.state && !this.#shell.isCollapsed();
468
- syncPageBlocker(this.#root, settings, live);
469
- }
543
+ this.#syncAnnotator();
470
544
  }
471
- log(kind, text, result) {
545
+ log(kind, text, result, actor) {
472
546
  const ms = this.#now();
473
547
  this.#glowCtl.markActivity(ms);
474
548
  if (this.#log === undefined)
@@ -484,9 +558,21 @@ export class Presenter {
484
558
  while (this.#runLog.length > this.#logMax)
485
559
  this.#runLog.shift();
486
560
  const ts = formatElapsed(ms - this.#logBaseMs);
487
- const handle = appendLogRow(this.#log, kind, trimmed, ts, this.#logMax);
561
+ const handle = appendLogRow(this.#log, kind, trimmed, ts, this.#logMax, actor);
488
562
  if (result !== undefined)
489
563
  handle.result(result);
564
+ const kept = {
565
+ kind,
566
+ text: trimmed,
567
+ ts,
568
+ at: ms - this.#logBaseMs,
569
+ ...(actor === undefined ? {} : { actor }),
570
+ ...(result === undefined ? {} : { result }),
571
+ };
572
+ this.#remembered.push(kept);
573
+ while (this.#remembered.length > this.#logMax)
574
+ this.#remembered.shift();
575
+ rememberLog(this.#remembered);
490
576
  if (this.#shell.isCollapsed())
491
577
  this.#shell.pulseFab(true);
492
578
  this.#renderTally();
@@ -494,6 +580,8 @@ export class Presenter {
494
580
  result: (r) => {
495
581
  handle.result(r);
496
582
  entry.result = r;
583
+ kept.result = r;
584
+ rememberLog(this.#remembered);
497
585
  this.#renderTally();
498
586
  },
499
587
  };
@@ -1,5 +1,8 @@
1
1
  import { isLastSlide } from './tour-view.js';
2
- export declare const TOUR_SEEN_KEY_PREFIX = "reticle.tour.seen.";
2
+ import { TourAnchor } from '@reticlehq/core/tour';
3
+ import { TOUR_SEEN_KEY_PREFIX } from '../../storage-keys.js';
4
+ export { TOUR_SEEN_KEY_PREFIX };
5
+ export declare const HUD_CONTROL_SELECTORS: Readonly<Partial<Record<TourAnchor, string>>>;
3
6
  export declare const tourSeenKey: (projectId: string) => string;
4
7
  export interface TourStorage {
5
8
  getItem: (key: string) => string | null;