@reticlehq/browser 3.6.0 → 3.7.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 (83) hide show
  1. package/dist/actions/action-report.d.ts +54 -0
  2. package/dist/actions/action-report.js +60 -0
  3. package/dist/actions/actions-dom.d.ts +1 -0
  4. package/dist/actions/actions-dom.js +12 -0
  5. package/dist/actions/actions-press.d.ts +1 -0
  6. package/dist/actions/actions-press.js +25 -1
  7. package/dist/actions/actions.d.ts +1 -37
  8. package/dist/actions/actions.js +24 -66
  9. package/dist/actions/appeared-text.js +3 -0
  10. package/dist/actions/danger-context.d.ts +2 -0
  11. package/dist/actions/danger-context.js +28 -2
  12. package/dist/actions/value-input.d.ts +1 -0
  13. package/dist/actions/value-input.js +11 -0
  14. package/dist/commands/commands.js +4 -0
  15. package/dist/dom/a11y.js +251 -10
  16. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  17. package/dist/dom/addressing/coverage-markers.js +13 -0
  18. package/dist/dom/dom-ignore.d.ts +2 -0
  19. package/dist/dom/dom-ignore.js +15 -2
  20. package/dist/dom/query.d.ts +1 -1
  21. package/dist/dom/query.js +78 -16
  22. package/dist/dom/render-page.d.ts +12 -0
  23. package/dist/dom/render-page.js +135 -0
  24. package/dist/dom/snapshot.js +55 -7
  25. package/dist/observers/dom.js +32 -16
  26. package/dist/observers/field.js +3 -21
  27. package/dist/observers/network.js +56 -7
  28. package/dist/observers/perf.js +53 -1
  29. package/dist/observers/route.js +6 -5
  30. package/dist/presenter/carousel/carousel.js +2 -7
  31. package/dist/presenter/carousel/panel-slides.d.ts +2 -4
  32. package/dist/presenter/carousel/panel-slides.js +17 -15
  33. package/dist/presenter/chrome/log-memory.d.ts +1 -0
  34. package/dist/presenter/chrome/presenter-log.d.ts +7 -0
  35. package/dist/presenter/chrome/presenter-log.js +48 -8
  36. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  37. package/dist/presenter/chrome/presenter-morph.js +34 -0
  38. package/dist/presenter/chrome/presenter-report-copy.d.ts +17 -4
  39. package/dist/presenter/chrome/presenter-report-copy.js +26 -4
  40. package/dist/presenter/hud-telemetry.js +55 -4
  41. package/dist/presenter/presenter-account-styles.js +3 -3
  42. package/dist/presenter/presenter-account.d.ts +27 -5
  43. package/dist/presenter/presenter-account.js +83 -6
  44. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  45. package/dist/presenter/presenter-agent-link.js +62 -0
  46. package/dist/presenter/presenter-chat-views.d.ts +27 -5
  47. package/dist/presenter/presenter-chat-views.js +312 -72
  48. package/dist/presenter/presenter-config.d.ts +2 -0
  49. package/dist/presenter/presenter-config.js +1 -0
  50. package/dist/presenter/presenter-controls.d.ts +0 -1
  51. package/dist/presenter/presenter-controls.js +13 -91
  52. package/dist/presenter/presenter-design-tokens.js +1 -1
  53. package/dist/presenter/presenter-frame.d.ts +1 -1
  54. package/dist/presenter/presenter-frame.js +8 -10
  55. package/dist/presenter/presenter-harness-row.d.ts +67 -0
  56. package/dist/presenter/presenter-harness-row.js +209 -0
  57. package/dist/presenter/presenter-personas.d.ts +35 -0
  58. package/dist/presenter/presenter-personas.js +95 -0
  59. package/dist/presenter/presenter-plan.js +25 -24
  60. package/dist/presenter/presenter-report-styles.js +40 -22
  61. package/dist/presenter/presenter-report.d.ts +2 -1
  62. package/dist/presenter/presenter-report.js +83 -17
  63. package/dist/presenter/presenter-settings-styles.js +8 -4
  64. package/dist/presenter/presenter-settings.d.ts +7 -6
  65. package/dist/presenter/presenter-settings.js +75 -34
  66. package/dist/presenter/presenter-shell-styles.js +12 -3
  67. package/dist/presenter/presenter-shell.d.ts +10 -4
  68. package/dist/presenter/presenter-shell.js +31 -23
  69. package/dist/presenter/presenter-styles.js +0 -2
  70. package/dist/presenter/presenter-workspace.js +10 -3
  71. package/dist/presenter/presenter.d.ts +3 -2
  72. package/dist/presenter/presenter.js +81 -20
  73. package/dist/registry/declared-platform.js +2 -0
  74. package/dist/reticle-inject.js +777 -700
  75. package/dist/reticle.js +36 -2
  76. package/dist/storage-keys.d.ts +3 -2
  77. package/dist/storage-keys.js +7 -3
  78. package/dist/transport/transport.js +5 -3
  79. package/dist/transport/unreachable-message.d.ts +1 -1
  80. package/dist/transport/unreachable-message.js +3 -5
  81. package/package.json +2 -2
  82. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  83. package/dist/presenter/carousel/offer-card.js +0 -56
@@ -4,8 +4,9 @@ import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
4
4
  import { resetHudDockPosition } from './presenter-drag.js';
5
5
  import { findDock, scheduleSyncDockLayout } from './presenter-dock-layout.js';
6
6
  import { SETTINGS_CSS } from './presenter-settings-styles.js';
7
+ import { creditsLeftText, creditsSpent, creditsUsedText } from '@reticlehq/core/hud';
7
8
  import { DISCOVERY_CALL_URL, FOUNDER_EMAIL, FOUNDER_MAILTO } from '@reticlehq/core';
8
- import { ACCOUNT_TEXT, accountControlHtml, } from './presenter-account.js';
9
+ import { ACCOUNT_TEXT, accountControlHtml, paintAccountSlot, } from './presenter-account.js';
9
10
  export { SETTINGS_CSS };
10
11
  export const OutputDetail = {
11
12
  MINIMAL: 'minimal',
@@ -131,39 +132,59 @@ function patchSettings(patch) {
131
132
  function settingsHelpIcon() {
132
133
  return hiIconHtml(PresenterIcon.HELP, PRESENTER_ICON_SIZE.HELP);
133
134
  }
134
- function settingsLabel(text, helpTitle) {
135
+ const SETTING_TEXT = {
136
+ outputDetail: ['Output detail', 'How much a copied run includes.'],
137
+ autoOpenChat: ['Open the log automatically', 'Opens this panel when a session starts.'],
138
+ showTimestamps: ['Show timestamps', 'Shows how long ago each log row happened.'],
139
+ showTally: ['Show pass/fail count', 'A score pill for this session in the toolbar.'],
140
+ reactComponents: ['React component names', 'Adds component names to copied runs.'],
141
+ blockPageInteractions: [
142
+ 'Block page while adding notes',
143
+ 'Clicks pin a note instead of using the app.',
144
+ ],
145
+ clearOnCopy: ['Clear notes after copying', 'Starts fresh once your notes are copied.'],
146
+ hideUntilRestart: ['Hide until reload', 'Hides the HUD until this page reloads.'],
147
+ harnessEnabled: [
148
+ 'Allow Reticle to drive',
149
+ 'Off by default. Unlocks at 80% instrumentation; lets Run Harness test this project.',
150
+ ],
151
+ reduceMotion: ['Reduce motion', 'Fewer HUD animations.'],
152
+ ambientGlow: ['Page glow', 'Tints the page edges while a session runs.'],
153
+ workspace: ['Workspace', 'The Reticle account this machine is signed in to.'],
154
+ };
155
+ function settingsLabel(text, helpTitle, desc = '') {
135
156
  const help = settingsHelpIcon();
136
- return `<span class="reticle-settings-label">${text}<button type="button" class="reticle-settings-help" data-reticle-settings-help title="${helpTitle}" aria-label="${helpTitle}">${help}</button></span>`;
157
+ const line = '' === desc ? '' : `<span class="reticle-settings-desc">${desc}</span>`;
158
+ return `<span class="reticle-settings-labelcol"><span class="reticle-settings-label">${text}<button type="button" class="reticle-settings-help" data-reticle-settings-help title="${helpTitle}" aria-label="${helpTitle}">${help}</button></span>${line}</span>`;
137
159
  }
138
- function settingsToggleRow(key, label, helpTitle, extra = '') {
160
+ function settingsToggleRow(key, helpTitle, extra = '') {
161
+ const [label, desc] = SETTING_TEXT[key];
139
162
  return `<div class="reticle-settings-row" ${extra}>
140
- ${settingsLabel(label, helpTitle)}
163
+ ${settingsLabel(label, helpTitle, desc)}
141
164
  <button type="button" class="reticle-settings-toggle" ${SETTING_KEY_ATTR}="${key}" role="switch" aria-checked="false"></button>
142
165
  </div>`;
143
166
  }
144
- function settingsCheckRow(key, label, checked) {
167
+ function settingsCheckRow(key, checked) {
168
+ const [label, desc] = SETTING_TEXT[key];
145
169
  const on = checked ? 'true' : 'false';
146
170
  return `<label class="reticle-settings-checkrow" data-reticle-check-row="${key}">
147
171
  <span class="reticle-settings-check" data-reticle-check="${key}" role="checkbox" aria-checked="${on}" tabindex="0"></span>
148
- <span class="reticle-settings-check-label">${label}</span>
172
+ <span class="reticle-settings-labelcol"><span class="reticle-settings-check-label">${label}</span><span class="reticle-settings-desc">${desc}</span></span>
149
173
  </label>`;
150
174
  }
151
175
  export const SETTINGS_ACCOUNT_ATTR = 'data-reticle-settings-account';
152
176
  const SETTINGS_ACCOUNT_ROW_ATTR = 'data-reticle-settings-account-row';
153
177
  const HELP_TEXT_ATTR = 'data-reticle-settings-helptext';
154
178
  const SETTINGS_HARNESS_ROW_ATTR = 'data-reticle-settings-harness-row';
155
- const HARNESS_HELP = 'Let Reticle drive this app by itself to find defects. Set here or in your dashboard — both write to the same place.';
156
- const HARNESS_LOCKED_HELP = 'The Harness is not available to this workspace right now. Every workspace gets free Harness credits each month: see Plan in your Reticle dashboard.';
179
+ const HARNESS_HELP = 'Let Reticle test this app by itself to find defects, from Run Harness or from your coding agent. Off blocks every run. Set here or in your dashboard — both write to the same place.';
180
+ export const HARNESS_LOCKED_HELP = 'The Harness is not available to this workspace right now: see Plan in your Reticle dashboard.';
157
181
  export function creditsLeft(credits) {
158
182
  if (credits === undefined)
159
183
  return '';
160
- const left = Math.max(0, credits.limit - credits.used);
161
- return 0 === left
162
- ? `All ${String(credits.limit)} Harness credits used this month`
163
- : `${String(left)} of ${String(credits.limit)} Harness credits left this month`;
184
+ return creditsSpent(credits) ? creditsUsedText(credits).said : creditsLeftText(credits);
164
185
  }
165
186
  const ACCOUNT_HELP = `Whether this machine is signed in to a Reticle workspace. Press ${ACCOUNT_TEXT.SIGNED_OUT} to sign in through your browser.`;
166
- export function paintHarnessRow(root, config) {
187
+ export function paintHarnessRow(root, config, gate) {
167
188
  const row = root.querySelector(`[${SETTINGS_HARNESS_ROW_ATTR}]`);
168
189
  if (!(row instanceof HTMLElement))
169
190
  return;
@@ -175,15 +196,20 @@ export function paintHarnessRow(root, config) {
175
196
  const toggle = row.querySelector(`[${SETTING_KEY_ATTR}="harnessEnabled"]`);
176
197
  if (!(toggle instanceof HTMLElement))
177
198
  return;
178
- const usable = config.harnessEntitled;
199
+ const locked = gate !== undefined && !gate.unlocked ? gate.reason : undefined;
200
+ const usable = config.harnessEntitled && locked === undefined;
179
201
  toggle.setAttribute('aria-checked', config.harnessEnabled && usable ? 'true' : 'false');
180
202
  toggle.setAttribute('aria-disabled', usable ? 'false' : 'true');
181
- const help = row.querySelector('[data-reticle-help]');
203
+ const help = row.querySelector('[data-reticle-settings-help]');
182
204
  const credits = creditsLeft(config.credits);
183
205
  if (help instanceof HTMLElement)
184
- help.title = usable
185
- ? [HARNESS_HELP, credits].filter((t) => 0 < t.length).join(' ')
186
- : HARNESS_LOCKED_HELP;
206
+ help.title =
207
+ locked ??
208
+ (usable
209
+ ? [HARNESS_HELP, credits].filter((t) => 0 < t.length).join(' ')
210
+ : creditsSpent(config.credits)
211
+ ? credits
212
+ : HARNESS_LOCKED_HELP);
187
213
  }
188
214
  export function paintSettingsAccount(root, account, dashboardUrl, details = {}) {
189
215
  const row = root.querySelector(`[${SETTINGS_ACCOUNT_ROW_ATTR}]`);
@@ -192,7 +218,7 @@ export function paintSettingsAccount(root, account, dashboardUrl, details = {})
192
218
  return;
193
219
  const html = accountControlHtml(account, { ...details, dashboardUrl }, true);
194
220
  row.hidden = 0 === html.length;
195
- slot.innerHTML = html;
221
+ paintAccountSlot(slot, html);
196
222
  }
197
223
  const FEEDBACK_TEXT = {
198
224
  EMAIL: 'Email the founder',
@@ -206,6 +232,17 @@ const KILL_TEXT = {
206
232
  ARMED: 'Click again to kill Reticle',
207
233
  WARNING: 'Disconnects Reticle from this page: no HUD, no agent, nothing recorded. To bring it back, restart your dev server or reload the page.',
208
234
  };
235
+ const SDK_VERSION_ATTR = 'data-reticle-sdk-version';
236
+ const SDK_VERSION_TEXT = {
237
+ UNKNOWN: 'Reticle SDK version unknown',
238
+ KNOWN: (version) => `Reticle SDK ${version}`,
239
+ };
240
+ export function paintSdkVersion(root, version) {
241
+ const line = root.querySelector(`[${SDK_VERSION_ATTR}]`);
242
+ if (line !== null)
243
+ line.textContent =
244
+ version === undefined ? SDK_VERSION_TEXT.UNKNOWN : SDK_VERSION_TEXT.KNOWN(version);
245
+ }
209
246
  export function settingsPanelHtml() {
210
247
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
211
248
  const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
@@ -227,27 +264,27 @@ export function settingsPanelHtml() {
227
264
  <div class="reticle-settings-body">
228
265
  <div class="reticle-settings-section">Session</div>
229
266
  <div class="reticle-settings-row">
230
- ${settingsLabel('Output Detail', outputHelp)}
267
+ ${settingsLabel(SETTING_TEXT.outputDetail[0], outputHelp, SETTING_TEXT.outputDetail[1])}
231
268
  <button type="button" class="reticle-settings-cycle" data-reticle-settings-cycle="outputDetail"><span data-reticle-cycle-label></span><span class="reticle-settings-dots" data-reticle-cycle-dots></span></button>
232
269
  </div>
233
- ${settingsToggleRow('autoOpenChat', 'Auto-open chat', autoChatHelp)}
234
- ${settingsToggleRow('showTimestamps', 'Show timestamps', timestampsHelp)}
235
- ${settingsToggleRow('showTally', 'Show verdict tally', tallyHelp)}
270
+ ${settingsToggleRow('autoOpenChat', autoChatHelp)}
271
+ ${settingsToggleRow('showTimestamps', timestampsHelp)}
272
+ ${settingsToggleRow('showTally', tallyHelp)}
236
273
  <div class="reticle-settings-section">Inspector</div>
237
- ${settingsToggleRow('reactComponents', 'React Components', reactHelp, 'data-reticle-settings-react-row')}
274
+ ${settingsToggleRow('reactComponents', reactHelp, 'data-reticle-settings-react-row')}
238
275
  <div class="reticle-settings-section">Interaction</div>
239
- ${settingsCheckRow('blockPageInteractions', 'Block page while adding notes', true)}
240
- ${settingsCheckRow('clearOnCopy', 'Clear notes after copying', false)}
241
- ${settingsToggleRow('hideUntilRestart', 'Hide until reload', hideHelp)}
242
- ${settingsToggleRow('harnessEnabled', 'Autonomous driving', harnessHelp, `${SETTINGS_HARNESS_ROW_ATTR} hidden`)}
243
- ${settingsToggleRow('reduceMotion', 'Reduce motion', motionHelp)}
276
+ ${settingsCheckRow('blockPageInteractions', true)}
277
+ ${settingsCheckRow('clearOnCopy', false)}
278
+ ${settingsToggleRow('hideUntilRestart', hideHelp)}
279
+ ${settingsToggleRow('harnessEnabled', harnessHelp, `${SETTINGS_HARNESS_ROW_ATTR} hidden`)}
280
+ ${settingsToggleRow('reduceMotion', motionHelp)}
244
281
  <div class="reticle-settings-section">Account</div>
245
282
  <div class="reticle-settings-row" ${SETTINGS_ACCOUNT_ROW_ATTR} hidden>
246
- ${settingsLabel('Workspace', ACCOUNT_HELP)}
283
+ ${settingsLabel(SETTING_TEXT.workspace[0], ACCOUNT_HELP, SETTING_TEXT.workspace[1])}
247
284
  <span ${SETTINGS_ACCOUNT_ATTR}></span>
248
285
  </div>
249
286
  <div class="reticle-settings-section">Status theme</div>
250
- ${settingsToggleRow('ambientGlow', 'Page glow', glowHelp)}
287
+ ${settingsToggleRow('ambientGlow', glowHelp)}
251
288
  <div class="reticle-settings-themes" data-reticle-settings-themes></div>
252
289
  <div class="reticle-settings-section">Help</div>
253
290
  <div class="reticle-settings-foot">
@@ -255,6 +292,7 @@ export function settingsPanelHtml() {
255
292
  <button type="button" class="reticle-settings-link" data-reticle-settings-mcp>MCP setup guide<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></button>
256
293
  <a class="reticle-settings-link" data-reticle-feedback-email href="${FOUNDER_MAILTO}" target="_blank" rel="noopener noreferrer" title="${FEEDBACK_TEXT.EMAIL_TITLE}">${FEEDBACK_TEXT.EMAIL}<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></a>
257
294
  <a class="reticle-settings-link" data-reticle-feedback-call href="${DISCOVERY_CALL_URL}" target="_blank" rel="noopener noreferrer" title="${FEEDBACK_TEXT.CALL_TITLE}">${FEEDBACK_TEXT.CALL}<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></a>
295
+ <p class="reticle-settings-kill-sub" ${SDK_VERSION_ATTR}>${SDK_VERSION_TEXT.UNKNOWN}</p>
258
296
  <div class="reticle-settings-kill-row">
259
297
  <button type="button" class="reticle-settings-kill" ${KILL_ATTR}><span data-reticle-kill-label>${KILL_TEXT.LABEL}</span></button>
260
298
  <p class="reticle-settings-kill-sub">${KILL_TEXT.WARNING}</p>
@@ -317,7 +355,10 @@ export class PresenterSettingsPanel {
317
355
  setHiIcon(closeBtn, PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
318
356
  }
319
357
  if (this.#btn !== undefined) {
358
+ const label = this.#btn.querySelector('.reticle-nav-label');
320
359
  setHiIcon(this.#btn, PresenterIcon.GEAR, PRESENTER_ICON_SIZE.TOOLBAR);
360
+ if (null !== label)
361
+ this.#btn.append(label);
321
362
  this.#btn.addEventListener('click', (e) => {
322
363
  e.stopPropagation();
323
364
  this.open();
@@ -461,11 +502,11 @@ export class PresenterSettingsPanel {
461
502
  applyPresenterSettings(root, activeSettings);
462
503
  this.#host.onSettingsChange?.(activeSettings);
463
504
  }
464
- paintHarness(config) {
505
+ paintHarness(config, gate) {
465
506
  const root = this.#panel;
466
507
  if (root === undefined)
467
508
  return;
468
- paintHarnessRow(root, config);
509
+ paintHarnessRow(root, config, gate);
469
510
  }
470
511
  teardown() {
471
512
  this.#root = undefined;
@@ -88,8 +88,7 @@ export const SHELL_CSS = `
88
88
  [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] [data-reticle-log]{
89
89
  pointer-events:none;}
90
90
  /* ...except a log with more than it shows: a feed nobody can scroll back through is not a log. */
91
- [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] [data-reticle-log][data-reticle-log-scrollable],
92
- [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] .reticle-flow-strip{
91
+ [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] [data-reticle-log][data-reticle-log-scrollable]{
93
92
  pointer-events:auto;}
94
93
  [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] :is(button,a,input,select,textarea){
95
94
  pointer-events:auto;}
@@ -132,6 +131,14 @@ export const SHELL_CSS = `
132
131
  so the two never overlap. The :empty rule keeps the gap closed when no account state was sent. */
133
132
  [${CHAT_PANEL}] .reticle-head-account{margin-left:auto;display:inline-flex;align-items:center;}
134
133
  [${CHAT_PANEL}] .reticle-head-account:empty{display:none;}
134
+ /* The quiet way to the platform: one line of small text before the avatar, cut short before it pushes. */
135
+ [${CHAT_PANEL}] .reticle-head-team{margin-left:auto;min-width:0;display:inline-flex;}
136
+ [${CHAT_PANEL}] .reticle-head-team:empty{display:none;}
137
+ [${CHAT_PANEL}] .reticle-head-team:not(:empty) + .reticle-head-account{margin-left:0;}
138
+ [${CHAT_PANEL}] .reticle-team-link{min-width:0;max-width:100%;overflow:hidden;padding:0;border:0;background:none;cursor:pointer;font:inherit;font-size:var(--reticle-hud-size-xs);font-weight:500;color:var(--reticle-hud-text-muted);text-decoration:none;white-space:nowrap;text-overflow:ellipsis;}
139
+ [${CHAT_PANEL}] .reticle-team-link span{display:block;overflow:hidden;text-overflow:ellipsis;}
140
+ [${CHAT_PANEL}] .reticle-team-link:hover{color:var(--reticle-hud-brand);}
141
+ [${CHAT_PANEL}] .reticle-team-link:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:2px;border-radius:var(--reticle-hud-radius-sm);}
135
142
  [${CHAT_PANEL}] .reticle-chat-brand .reticle-mark{height:14px;width:auto;}
136
143
  [${CHAT_PANEL}] .reticle-chat-brandname{font-size:12.5px;font-weight:600;letter-spacing:.01em;}
137
144
  [${CHAT_PANEL}] .reticle-view-heading{display:flex;justify-content:space-between;align-items:baseline;gap:8px;min-height:28px;box-sizing:border-box;padding:6px 12px 4px;border-bottom:1px solid var(--reticle-hud-border);}
@@ -255,7 +262,7 @@ export const SHELL_CSS = `
255
262
  [${HUD}] .reticle-toolbar-actions{
256
263
  display:inline-flex;align-items:center;gap:2px;flex:none;
257
264
  padding:2px;border-radius:999px;background:rgba(0,0,0,.2);}
258
- [${HUD}] .reticle-toolbar-chrome{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));align-items:center;gap:var(--reticle-hud-space-1);flex:1;min-width:0;}
265
+ [${HUD}] .reticle-toolbar-chrome{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);align-items:center;gap:var(--reticle-hud-space-1);flex:1;min-width:0;}
259
266
  [${HUD}] .reticle-tb-sep{width:1px;height:14px;background:rgba(255,255,255,.12);margin:0 4px;flex:none;align-self:center;}
260
267
 
261
268
  [${HUD}] .reticle-tb-wrap{position:relative;display:flex;align-items:center;justify-content:center;overflow:visible;}
@@ -370,6 +377,8 @@ export const SHELL_CSS = `
370
377
  */
371
378
  [${OVERLAY}][${STATE}="unreachable"]{--reticle-state:var(--reticle-faint);}
372
379
  [${OVERLAY}][${STATE}="unreachable"] [${CHAT_PANEL}] .reticle-act-dot{animation:none;}
380
+ /* The one status that is a sentence to act on: two lines, full contrast, the detail on hover. */
381
+ [${OVERLAY}][${STATE}="unreachable"] [${CHAT_PANEL}] .reticle-act{white-space:normal;line-height:1.35;color:var(--reticle-fg);cursor:help;}
373
382
  [${OVERLAY}][${STATE}="paused"] [${CHAT_PANEL}] .reticle-act-dot,
374
383
  [${OVERLAY}][${STATE}="ended"] [${CHAT_PANEL}] .reticle-act-dot{animation:none;}
375
384
  @keyframes reticle-idle-pulse{0%,100%{opacity:.45;transform:scale(.92)}50%{opacity:1;transform:scale(1)}}
@@ -1,8 +1,8 @@
1
- import type { HarnessConfig } from '@reticlehq/core';
1
+ import type { AgentLink, HarnessConfig, HarnessDrive } from '@reticlehq/core';
2
+ import type { HarnessGateView } from './presenter-harness-row.js';
2
3
  import { PresenterReport } from './presenter-report.js';
3
4
  import type { AccountState } from '@reticlehq/core';
4
5
  import { type AccountDetails } from './presenter-account.js';
5
- import { type OfferState } from './carousel/offer-card.js';
6
6
  import { type ChatView } from './presenter-chat-views.js';
7
7
  import type { AnnotationItem } from '../review/annotator.js';
8
8
  import { type SettingsHost } from './presenter-settings.js';
@@ -14,17 +14,23 @@ interface HudShellCallbacks {
14
14
  onCollapse?: () => void;
15
15
  onSyncNow?: () => void;
16
16
  onHarness?: (enabled: boolean) => void;
17
+ onHarnessRun?: (persona?: string) => void;
18
+ onHarnessStop?: () => void;
19
+ onHarnessLink?: () => void;
17
20
  onSignIn?: () => void;
18
21
  settings?: SettingsHost;
19
22
  }
20
23
  export declare class HudShell {
21
24
  #private;
22
- paintHarness(config: HarnessConfig | undefined): void;
25
+ setProjectId(projectId: string | undefined): void;
26
+ paintHarnessDrive(drive: HarnessDrive | undefined): void;
27
+ paintAgent(link: AgentLink | undefined): void;
28
+ paintHarness(config: HarnessConfig | undefined, gate?: HarnessGateView): void;
23
29
  paintImpact(verdicts: number): void;
24
30
  paintAnnotations(items: readonly AnnotationItem[]): void;
25
31
  openView(view: ChatView): void;
26
32
  paintAccount(account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
27
- paintOffer(offer: OfferState | undefined, notices?: readonly unknown[]): void;
33
+ paintNotices(notices?: readonly unknown[]): void;
28
34
  constructor(callbacks?: HudShellCallbacks);
29
35
  static dockHtml(actStripHtml: string, bannerHtml: string, logAttr: string, flowsHtml: string, footHtml: string): string;
30
36
  get report(): PresenterReport;
@@ -1,10 +1,9 @@
1
1
  import { appModalOpen } from '../dom/dom-ignore.js';
2
2
  import { PresenterReport, reportPanelHtml } from './presenter-report.js';
3
3
  import { paintToolbarAccount, TOOLBAR_ACCOUNT_ATTR } from './presenter-workspace.js';
4
- import { mountAccountControl } from './presenter-account.js';
4
+ import { mountAccountControl, TEAM_SLOT_ATTR } from './presenter-account.js';
5
5
  import { paintSettingsAccount } from './presenter-settings.js';
6
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
- import { offerDismissed } from './carousel/offer-card.js';
8
7
  import { paintCarousel } from './carousel/carousel.js';
9
8
  import { panelSlides } from './carousel/panel-slides.js';
10
9
  import { paintRail, RAIL_HTML, RAIL_PROMO_ATTR } from './presenter-frame.js';
@@ -14,7 +13,7 @@ import { installHudDragHandles, installHudPositionGuards } from './presenter-dra
14
13
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
15
14
  import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
16
15
  import { HUD_SURFACE_CLASS, HUD_LOG_WELL_CLASS } from './chrome/presenter-hud-chrome.js';
17
- import { ChatViews, CHAT_VIEWS_NAV_HTML, ANNOTATIONS_HTML, FLOWS_PAGE_HTML, platformBase, } from './presenter-chat-views.js';
16
+ import { ChatViews, CHAT_VIEWS_NAV_HTML, ANNOTATIONS_HTML, FLOWS_PAGE_HTML, HARNESS_PAGE_HTML, NAV_LABEL, navLabelHtml, platformBase, } from './presenter-chat-views.js';
18
17
  import { PresenterSettingsPanel, settingsPanelHtml, } from './presenter-settings.js';
19
18
  const DRAG_HANDLE_CLASS = 'reticle-toolbar-drag';
20
19
  const TRANSITION_LOCK_MS = 120;
@@ -23,16 +22,10 @@ const CHAT_PILL_LABEL = 'Open agent chat';
23
22
  const SETTINGS_LABEL = 'Settings';
24
23
  const AGENT_LOG_TITLE = 'Reticle';
25
24
  const EXIT_LABEL = 'Exit';
26
- function paintPromo(root, offer, notices = [], base) {
25
+ function paintPromo(root, notices = [], base) {
27
26
  const target = root.querySelector(`[${RAIL_PROMO_ATTR}]`);
28
27
  if (null === target)
29
28
  return;
30
- let dismissed = false;
31
- try {
32
- dismissed = offerDismissed(window.localStorage);
33
- }
34
- catch {
35
- }
36
29
  let storage;
37
30
  try {
38
31
  storage = window.sessionStorage;
@@ -40,7 +33,7 @@ function paintPromo(root, offer, notices = [], base) {
40
33
  catch {
41
34
  storage = undefined;
42
35
  }
43
- paintCarousel(target, panelSlides(offer, dismissed, notices, base), storage);
36
+ paintCarousel(target, panelSlides(notices, base), storage);
44
37
  }
45
38
  export class HudShell {
46
39
  #root;
@@ -67,15 +60,23 @@ export class HudShell {
67
60
  });
68
61
  #callbacks;
69
62
  #pushedAccount;
70
- #pushedOffer;
71
63
  #pushedNotices = [];
72
64
  #pushedHarness;
73
65
  #promoBase;
74
66
  #accountTeardown;
75
- paintHarness(config) {
67
+ setProjectId(projectId) {
68
+ this.#chatViews.setProjectId(projectId);
69
+ }
70
+ paintHarnessDrive(drive) {
71
+ this.#chatViews.paintDrive(drive);
72
+ }
73
+ paintAgent(link) {
74
+ this.#chatViews.paintAgent(link);
75
+ }
76
+ paintHarness(config, gate) {
76
77
  this.#pushedHarness = config;
77
- this.#settings.paintHarness(config);
78
- this.#chatViews.paintHarness(config);
78
+ this.#settings.paintHarness(config, gate);
79
+ this.#chatViews.paintHarness(config, gate);
79
80
  this.#repaintPromoIfMoved();
80
81
  }
81
82
  #promoLinks() {
@@ -86,7 +87,7 @@ export class HudShell {
86
87
  if (this.#root === undefined || base === this.#promoBase)
87
88
  return;
88
89
  this.#promoBase = base;
89
- paintPromo(this.#root, this.#pushedOffer, this.#pushedNotices, base);
90
+ paintPromo(this.#root, this.#pushedNotices, base);
90
91
  }
91
92
  paintImpact(verdicts) {
92
93
  this.#chatViews.paintImpact(verdicts);
@@ -116,16 +117,15 @@ export class HudShell {
116
117
  this.#chatViews.paintAccount(account);
117
118
  this.#repaintPromoIfMoved();
118
119
  }
119
- paintOffer(offer, notices = []) {
120
- this.#pushedOffer = offer;
120
+ paintNotices(notices = []) {
121
121
  this.#pushedNotices = notices;
122
122
  this.#promoBase = this.#promoLinks();
123
123
  if (this.#root !== undefined)
124
- paintPromo(this.#root, offer, notices, this.#promoBase);
124
+ paintPromo(this.#root, notices, this.#promoBase);
125
125
  }
126
126
  constructor(callbacks = {}) {
127
127
  this.#callbacks = callbacks;
128
- this.#chatViews = new ChatViews((enabled) => callbacks.onHarness?.(enabled), () => {
128
+ this.#chatViews = new ChatViews(() => {
129
129
  this.#chatViews.closePage();
130
130
  this.closeChat();
131
131
  this.#report.open();
@@ -140,6 +140,11 @@ export class HudShell {
140
140
  this.closeChat();
141
141
  this.#settings.close();
142
142
  this.#report.close();
143
+ }, {
144
+ run: (persona) => callbacks.onHarnessRun?.(persona),
145
+ stop: () => callbacks.onHarnessStop?.(),
146
+ setHarness: (on) => callbacks.onHarness?.(on),
147
+ link: () => callbacks.onHarnessLink?.(),
143
148
  });
144
149
  this.#settings = new PresenterSettingsPanel({
145
150
  ...callbacks.settings,
@@ -158,6 +163,7 @@ export class HudShell {
158
163
  <div ${CHAT_PANEL_ATTR} class="reticle-chat-panel ${HUD_SURFACE_CLASS}" role="region" aria-label="Agent Log" aria-hidden="true">
159
164
  <div class="reticle-chat-head">
160
165
  <span class="reticle-chat-brand">${MARK_SVG}<span class="reticle-chat-brandname">${AGENT_LOG_TITLE}</span></span>
166
+ <span ${TEAM_SLOT_ATTR} class="reticle-head-team"></span>
161
167
  <span ${TOOLBAR_ACCOUNT_ATTR} class="reticle-head-account"></span>
162
168
  </div>
163
169
  <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>
@@ -171,6 +177,7 @@ export class HudShell {
171
177
  ${footHtml}
172
178
  </div>
173
179
  ${FLOWS_PAGE_HTML}
180
+ ${HARNESS_PAGE_HTML}
174
181
  ${ANNOTATIONS_HTML}
175
182
  <button type="button" ${CHAT_PILL_ATTR} class="reticle-chat-pill" title="${CHAT_PILL_LABEL}" aria-label="${CHAT_PILL_LABEL}">
176
183
  ${MARK_SVG}
@@ -188,11 +195,11 @@ export class HudShell {
188
195
  <div class="reticle-toolbar-chrome">
189
196
  ${CHAT_VIEWS_NAV_HTML}
190
197
  <div class="reticle-tb-wrap">
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>
198
+ <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}${navLabelHtml(NAV_LABEL.SETTINGS)}</button>
192
199
  <span class="reticle-tb-tip">${SETTINGS_LABEL}</span>
193
200
  </div>
194
201
  <div class="reticle-tb-wrap">
195
- <button type="button" data-reticle-min-btn class="reticle-tb-btn" title="${EXIT_LABEL}" aria-label="${EXIT_LABEL}">${exit}</button>
202
+ <button type="button" data-reticle-min-btn class="reticle-tb-btn" title="${EXIT_LABEL}" aria-label="${EXIT_LABEL}">${exit}${navLabelHtml(NAV_LABEL.HIDE)}</button>
196
203
  <span class="reticle-tb-tip">${EXIT_LABEL}<span class="reticle-tb-kbd">Esc</span></span>
197
204
  </div>
198
205
  </div>
@@ -218,6 +225,7 @@ export class HudShell {
218
225
  if (impact !== null) {
219
226
  const on = '1' === root.getAttribute(REPORT_ATTR);
220
227
  impact.setAttribute('data-active', on ? '1' : '0');
228
+ this.#chatViews.setImpactOpen(on);
221
229
  if (on)
222
230
  impact.setAttribute('aria-current', 'page');
223
231
  else
@@ -279,7 +287,7 @@ export class HudShell {
279
287
  this.#report.mount(root);
280
288
  this.#chatViews.mount(root);
281
289
  this.#promoBase = this.#promoLinks();
282
- paintPromo(root, this.#pushedOffer, this.#pushedNotices, this.#promoBase);
290
+ paintPromo(root, this.#pushedNotices, this.#promoBase);
283
291
  this.#toggleSync = new MutationObserver(() => this.#syncToolbarToggles());
284
292
  this.#toggleSync.observe(root, {
285
293
  attributes: true,
@@ -6,7 +6,6 @@ import { SETTINGS_CSS } from './presenter-settings-styles.js';
6
6
  import { ACCOUNT_CSS } from './presenter-account-styles.js';
7
7
  import { HUD_CHROME_CSS, HUD_LOG_WELL_CSS } from './chrome/presenter-hud-chrome.js';
8
8
  import { REPORT_CSS } from './presenter-report-styles.js';
9
- import { OFFER_CSS } from './carousel/offer-card.js';
10
9
  import { TALK_CSS } from './carousel/talk-card.js';
11
10
  import { CAROUSEL_CSS } from './carousel/carousel.js';
12
11
  import { CHAT_VIEWS_CSS } from './presenter-chat-views.js';
@@ -71,7 +70,6 @@ ${SHELL_CSS}
71
70
  ${SETTINGS_CSS}
72
71
  ${REPORT_CSS}
73
72
  ${ACCOUNT_CSS}
74
- ${OFFER_CSS}
75
73
  ${TALK_CSS}
76
74
  ${CAROUSEL_CSS}
77
75
  ${LOG_CSS}
@@ -1,6 +1,6 @@
1
1
  import { RETICLE_ROOT_GLOBAL, RETICLE_URL_PARAM } from '@reticlehq/core';
2
2
  import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './icons/presenter-icons.js';
3
- import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, accountControlHtml, } from './presenter-account.js';
3
+ import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, accountControlHtml, paintAccountSlot, teamLinkHtml, TEAM_SLOT_ATTR, } from './presenter-account.js';
4
4
  const WORKSPACE_BTN_ATTR = 'data-reticle-workspace-btn';
5
5
  const WORKSPACE_MENU_ATTR = 'data-reticle-workspace-menu';
6
6
  const WORKSPACE_NAME_ATTR = 'data-reticle-workspace-name';
@@ -10,6 +10,8 @@ const WORKSPACE_COPY_ATTR = 'data-reticle-workspace-copy';
10
10
  export const TOOLBAR_ACCOUNT_ATTR = 'data-reticle-toolbar-account';
11
11
  const COPIED_MS = 1_200;
12
12
  const WORKSPACE_LABEL = 'Workspace';
13
+ const CHIP_KEY = 'Project';
14
+ const CHIP_TITLE = 'The project folder this page is served from, which Reticle reports to. Click for its path.';
13
15
  const PROJECT_LABEL = 'Project';
14
16
  const COPY_PATH_LABEL = 'Copy path';
15
17
  const COPIED_PATH_LABEL = 'Copied';
@@ -48,8 +50,9 @@ export function workspaceRowHtml() {
48
50
  const caret = hiIconHtml(PresenterIcon.CARET_DOWN, PRESENTER_ICON_SIZE.HELP);
49
51
  const copyIcon = hiIconHtml(PresenterIcon.COPY, PRESENTER_ICON_SIZE.HELP);
50
52
  return `<div class="reticle-workspace-wrap">
51
- <button type="button" class="reticle-workspace" ${WORKSPACE_BTN_ATTR} aria-haspopup="true" aria-expanded="false" title="${WORKSPACE_LABEL}">
53
+ <button type="button" class="reticle-workspace" ${WORKSPACE_BTN_ATTR} aria-haspopup="true" aria-expanded="false" title="${CHIP_TITLE}">
52
54
  <span class="reticle-workspace-icon" aria-hidden="true">${folderIcon}</span>
55
+ <span class="reticle-workspace-k">${CHIP_KEY}</span>
53
56
  <span class="reticle-workspace-name" ${WORKSPACE_NAME_ATTR}>${WORKSPACE_FALLBACK}</span>
54
57
  <span class="reticle-workspace-caret" aria-hidden="true">${caret}</span>
55
58
  </button>
@@ -193,8 +196,12 @@ export function mountWorkspaceSelector(root) {
193
196
  };
194
197
  }
195
198
  export function paintToolbarAccount(root, account, dashboardUrl, details = {}) {
199
+ const team = root.querySelector(`[${TEAM_SLOT_ATTR}]`);
200
+ if (team instanceof HTMLElement)
201
+ paintAccountSlot(team, teamLinkHtml(account, dashboardUrl));
196
202
  const slot = root.querySelector(`[${TOOLBAR_ACCOUNT_ATTR}]`);
197
203
  if (!(slot instanceof HTMLElement))
198
204
  return;
199
- slot.innerHTML = accountControlHtml(account, { ...details, dashboardUrl }, true);
205
+ const door = !(team instanceof HTMLElement);
206
+ paintAccountSlot(slot, accountControlHtml(account, { ...details, dashboardUrl }, door));
200
207
  }
@@ -17,6 +17,7 @@ export declare class Presenter {
17
17
  get sessionActive(): boolean;
18
18
  setState(state: SessionState, text?: string, tone?: PresenterTone): void;
19
19
  refilterFlows(): void;
20
+ showSdkVersion(version: string | undefined): void;
20
21
  handlePush(command: {
21
22
  name: string;
22
23
  args: Record<string, unknown>;
@@ -38,8 +39,8 @@ export declare class Presenter {
38
39
  placeHud(visibility: HudVisibility | undefined, corner: HudCorner | undefined): string[];
39
40
  setIdleEndMs(ms: number): void;
40
41
  runState(): PresenterRunState | Record<string, unknown>;
41
- log(kind: LogKind, text: string, result?: LogResult, actor?: LogActor): LogHandle | undefined;
42
- narrate(text: string, level?: string): LogHandle | undefined;
42
+ log(kind: LogKind, text: string, result?: LogResult, actor?: LogActor, summary?: boolean): LogHandle | undefined;
43
+ narrate(text: string, level?: string, actor?: LogActor, summary?: boolean): LogHandle | undefined;
43
44
  setThrottled(throttled: boolean): void;
44
45
  beforeAct(refId: string, action: string, label: string): Promise<void>;
45
46
  }