@reticlehq/browser 3.1.0 → 3.3.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 (71) hide show
  1. package/dist/actions/actions-dom.d.ts +2 -1
  2. package/dist/actions/actions-dom.js +28 -41
  3. package/dist/actions/actions.d.ts +0 -1
  4. package/dist/actions/actions.js +13 -7
  5. package/dist/actions/value-input.js +14 -5
  6. package/dist/connect-options.d.ts +1 -0
  7. package/dist/dom/addressing/source.d.ts +2 -1
  8. package/dist/dom/dom-ignore.d.ts +1 -0
  9. package/dist/dom/dom-ignore.js +13 -0
  10. package/dist/dom/query.js +28 -9
  11. package/dist/dom/snapshot.d.ts +1 -1
  12. package/dist/observers/console.js +38 -13
  13. package/dist/observers/departure.js +16 -8
  14. package/dist/observers/dom.js +3 -1
  15. package/dist/observers/download.js +3 -2
  16. package/dist/observers/error-repeats.d.ts +18 -0
  17. package/dist/observers/error-repeats.js +41 -0
  18. package/dist/observers/field.d.ts +2 -0
  19. package/dist/observers/field.js +86 -0
  20. package/dist/observers/frames.js +3 -4
  21. package/dist/observers/install-all.js +2 -0
  22. package/dist/observers/net-detail/network-body.d.ts +2 -0
  23. package/dist/observers/net-detail/network-body.js +24 -7
  24. package/dist/observers/network.js +49 -8
  25. package/dist/observers/route.js +4 -4
  26. package/dist/observers/state.js +18 -8
  27. package/dist/presenter/carousel/carousel.d.ts +13 -0
  28. package/dist/presenter/carousel/carousel.js +126 -0
  29. package/dist/presenter/carousel/offer-card.d.ts +21 -0
  30. package/dist/presenter/carousel/offer-card.js +55 -0
  31. package/dist/presenter/carousel/panel-slides.d.ts +7 -0
  32. package/dist/presenter/carousel/panel-slides.js +10 -0
  33. package/dist/presenter/carousel/talk-card.d.ts +8 -0
  34. package/dist/presenter/carousel/talk-card.js +20 -0
  35. package/dist/presenter/chrome/presenter-log.d.ts +2 -1
  36. package/dist/presenter/chrome/presenter-log.js +14 -3
  37. package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -1
  38. package/dist/presenter/chrome/presenter-report-copy.js +1 -1
  39. package/dist/presenter/hud-telemetry.d.ts +3 -0
  40. package/dist/presenter/hud-telemetry.js +87 -0
  41. package/dist/presenter/presenter-account.js +1 -1
  42. package/dist/presenter/presenter-config.d.ts +5 -1
  43. package/dist/presenter/presenter-config.js +4 -1
  44. package/dist/presenter/presenter-controls.d.ts +1 -0
  45. package/dist/presenter/presenter-controls.js +12 -1
  46. package/dist/presenter/presenter-report-styles.js +4 -0
  47. package/dist/presenter/presenter-report.js +8 -7
  48. package/dist/presenter/presenter-settings-styles.js +2 -1
  49. package/dist/presenter/presenter-settings.d.ts +6 -4
  50. package/dist/presenter/presenter-settings.js +51 -5
  51. package/dist/presenter/presenter-shell-styles.js +16 -0
  52. package/dist/presenter/presenter-shell.d.ts +7 -0
  53. package/dist/presenter/presenter-shell.js +65 -5
  54. package/dist/presenter/presenter-styles.js +6 -0
  55. package/dist/presenter/presenter-workspace.d.ts +0 -7
  56. package/dist/presenter/presenter-workspace.js +7 -7
  57. package/dist/presenter/presenter.js +46 -10
  58. package/dist/presenter/tour/tour-view.js +7 -3
  59. package/dist/presenter/tour/tour.d.ts +3 -0
  60. package/dist/presenter/tour/tour.js +26 -2
  61. package/dist/recorder/recorder.js +25 -22
  62. package/dist/reticle-inject.d.ts +2 -0
  63. package/dist/reticle-inject.js +1337 -0
  64. package/dist/reticle.js +11 -1
  65. package/dist/review/annotator.d.ts +3 -0
  66. package/dist/review/annotator.js +37 -37
  67. package/dist/review/marks-for-agent.d.ts +9 -0
  68. package/dist/review/marks-for-agent.js +12 -0
  69. package/dist/util/captured-method.d.ts +0 -1
  70. package/dist/util/captured-method.js +1 -1
  71. package/package.json +8 -4
@@ -70,7 +70,7 @@ export const LOG_CSS = `
70
70
  border-radius:16px 16px 4px 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,.08);}
71
71
  [data-reticle-log-row][data-kind="human"] .reticle-log-text{color:var(--reticle-fg);font-size:12px;line-height:1.45;}
72
72
  `;
73
- export function scrollLogToLatest(container) {
73
+ function scrollLogToLatest(container) {
74
74
  container.scrollTop = container.scrollHeight;
75
75
  }
76
76
  export function settleLogAtLatest(container) {
@@ -81,6 +81,18 @@ export function settleLogAtLatest(container) {
81
81
  });
82
82
  nativeSetTimeout(() => scrollLogToLatest(container), LOG_SETTLE_MS);
83
83
  }
84
+ function logRows(container) {
85
+ return [...container.children].filter((el) => el.hasAttribute(DATA_RETICLE_LOG_ROW));
86
+ }
87
+ export function trimLogRows(container, max) {
88
+ const rows = logRows(container);
89
+ for (const row of rows.slice(0, Math.max(0, rows.length - max)))
90
+ row.remove();
91
+ }
92
+ export function clearLogRows(container) {
93
+ for (const row of logRows(container))
94
+ row.remove();
95
+ }
84
96
  export function clampLogMax(n) {
85
97
  if (n === undefined || !Number.isFinite(n) || n <= 0)
86
98
  return DEFAULT_LOG_MAX;
@@ -135,8 +147,7 @@ export function appendLogRow(container, kind, text, ts, logMax) {
135
147
  rowNodes.push(resEl);
136
148
  row.append(...rowNodes);
137
149
  container.appendChild(row);
138
- while (container.childElementCount > logMax)
139
- container.firstElementChild?.remove();
150
+ trimLogRows(container, logMax);
140
151
  requestAnimationFrame(() => {
141
152
  scrollLogToLatest(container);
142
153
  });
@@ -5,7 +5,6 @@ export declare const REPORT_LINKS: {
5
5
  readonly GITHUB: "https://github.com/reticlehq/reticle";
6
6
  readonly DISCORD: "https://discord.gg/BwAbzv9ZRz";
7
7
  };
8
- export declare const SHARE_VIA_HANDLE = "";
9
8
  export declare const REPORT_TEXT: {
10
9
  readonly TITLE: "Impact";
11
10
  readonly PROJECT: "This project";
@@ -5,7 +5,7 @@ export const REPORT_LINKS = {
5
5
  GITHUB: 'https://github.com/reticlehq/reticle',
6
6
  DISCORD: 'https://discord.gg/BwAbzv9ZRz',
7
7
  };
8
- export const SHARE_VIA_HANDLE = '';
8
+ const SHARE_VIA_HANDLE = '';
9
9
  export const REPORT_TEXT = {
10
10
  TITLE: 'Impact',
11
11
  PROJECT: 'This project',
@@ -0,0 +1,3 @@
1
+ import { type HudUseData } from '@reticlehq/core';
2
+ export declare function controlIdOf(el: Element): string | undefined;
3
+ export declare function installHudTelemetry(doc: Document, root: Element, report: (use: HudUseData) => void): () => void;
@@ -0,0 +1,87 @@
1
+ import { HUD_KEYED_ATTRS, isHudControl } from '@reticlehq/core/hud';
2
+ import { HudPanel, HudToggle, HudView } from '@reticlehq/core';
3
+ import { isReticleUi } from '../dom/dom-ignore.js';
4
+ import { nativeSetTimeout } from '../timers/native/native-timers.js';
5
+ import { CHAT_ATTR, MIN_ATTR, REPORT_ATTR, SETTINGS_ATTR } from './presenter-config.js';
6
+ const PREFIX = 'data-reticle-';
7
+ const TOGGLE_ROLES = new Set(['switch', 'checkbox']);
8
+ export function controlIdOf(el) {
9
+ for (const attr of Array.from(el.attributes)) {
10
+ if (!attr.name.startsWith(PREFIX))
11
+ continue;
12
+ const name = attr.name.slice(PREFIX.length);
13
+ const id = HUD_KEYED_ATTRS.includes(name)
14
+ ? `${name}.${attr.value}`
15
+ : name;
16
+ if (isHudControl(id))
17
+ return id;
18
+ }
19
+ return undefined;
20
+ }
21
+ function controlOf(path) {
22
+ for (const node of path) {
23
+ if (!(node instanceof Element))
24
+ continue;
25
+ if (controlIdOf(node) !== undefined)
26
+ return node;
27
+ }
28
+ return undefined;
29
+ }
30
+ function viewOf(root) {
31
+ if ('1' === root.getAttribute(MIN_ATTR))
32
+ return { view: HudView.BUBBLE, panel: HudPanel.NONE };
33
+ if ('1' === root.getAttribute(SETTINGS_ATTR)) {
34
+ return { view: HudView.EXPANDED, panel: HudPanel.SETTINGS };
35
+ }
36
+ if ('1' === root.getAttribute(REPORT_ATTR)) {
37
+ return { view: HudView.EXPANDED, panel: HudPanel.REPORT };
38
+ }
39
+ if ('1' === root.getAttribute(CHAT_ATTR))
40
+ return { view: HudView.EXPANDED, panel: HudPanel.CHAT };
41
+ return { view: HudView.COLLAPSED, panel: HudPanel.NONE };
42
+ }
43
+ export function installHudTelemetry(doc, root, report) {
44
+ const send = (use) => {
45
+ try {
46
+ report(use);
47
+ }
48
+ catch {
49
+ }
50
+ };
51
+ const onPress = (event) => {
52
+ const el = controlOf(event.composedPath());
53
+ if (el === undefined || !isReticleUi(el))
54
+ return;
55
+ const control = controlIdOf(el);
56
+ if (control === undefined)
57
+ return;
58
+ if (!TOGGLE_ROLES.has(el.getAttribute('role') ?? '')) {
59
+ send({ control });
60
+ return;
61
+ }
62
+ nativeSetTimeout(() => {
63
+ const on = 'true' === el.getAttribute('aria-checked');
64
+ send({ control, toggle: on ? HudToggle.ON : HudToggle.OFF });
65
+ }, 0);
66
+ };
67
+ let last = '';
68
+ const onChange = () => {
69
+ const now = viewOf(root);
70
+ const key = `${now.view}/${now.panel}`;
71
+ if (key === last)
72
+ return;
73
+ last = key;
74
+ send(now);
75
+ };
76
+ doc.addEventListener('click', onPress, true);
77
+ const observer = new MutationObserver(onChange);
78
+ observer.observe(root, {
79
+ attributes: true,
80
+ attributeFilter: [MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR, REPORT_ATTR],
81
+ });
82
+ onChange();
83
+ return () => {
84
+ doc.removeEventListener('click', onPress, true);
85
+ observer.disconnect();
86
+ };
87
+ }
@@ -77,7 +77,7 @@ function accountMenuHtml(account, details) {
77
77
  detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined);
78
78
  const linked = details.dashboardUrl !== undefined && isSafeDashboardUrl(details.dashboardUrl);
79
79
  const dashboard = linked
80
- ? `<a class="reticle-account-action" href="${esc(details.dashboardUrl ?? '')}" target="_blank" rel="noreferrer noopener" title="${ACCOUNT_TEXT.DASHBOARD_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.HELP)}<span>${ACCOUNT_TEXT.DASHBOARD_ACTION}</span></a>`
80
+ ? `<a class="reticle-account-action" data-reticle-account-dashboard href="${esc(details.dashboardUrl ?? '')}" target="_blank" rel="noreferrer noopener" title="${ACCOUNT_TEXT.DASHBOARD_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.HELP)}<span>${ACCOUNT_TEXT.DASHBOARD_ACTION}</span></a>`
81
81
  :
82
82
  `<button type="button" class="reticle-account-action reticle-account-action--hint" ${ACCOUNT_SIGNIN_ATTR} data-reticle-copy="${ACCOUNT_TEXT.LINK_COMMAND}" title="${ACCOUNT_TEXT.LINK_HINT}"><span>${ACCOUNT_TEXT.LINK_HINT}</span></button>`;
83
83
  const signOut = `<button type="button" class="reticle-account-action" ${ACCOUNT_SIGNOUT_ATTR} data-reticle-copy="${ACCOUNT_TEXT.SIGNOUT_COMMAND}" title="${ACCOUNT_TEXT.SIGNOUT_TITLE}"><span>${ACCOUNT_TEXT.SIGNOUT_ACTION}</span></button>`;
@@ -1,4 +1,4 @@
1
- import type { HumanControlKind } from '@reticlehq/core';
1
+ import type { HudUseData, HumanControlKind } from '@reticlehq/core';
2
2
  export declare const BorderMode: {
3
3
  readonly SESSION: "session";
4
4
  readonly BUSY: "busy";
@@ -21,6 +21,7 @@ export interface PresenterOptions {
21
21
  border?: BorderMode;
22
22
  logMax?: number;
23
23
  onControl?: ControlHandler;
24
+ onHudUse?: (use: HudUseData) => void;
24
25
  endedFadeMs?: number;
25
26
  }
26
27
  export declare const DEFAULT_PACE = 450;
@@ -65,6 +66,8 @@ export declare const Placement: {
65
66
  };
66
67
  export declare const MARKERS_BTN_ATTR = "data-reticle-markers-btn";
67
68
  export declare const CLEAR_MARKS_ATTR = "data-reticle-clear-marks";
69
+ export declare const COPY_MARKS_ATTR = "data-reticle-copy-marks";
70
+ export declare const MARKS_ROW_ATTR = "data-reticle-marks-row";
68
71
  export declare const MARK_COUNT_ATTR = "data-reticle-mark-count";
69
72
  export declare const ANNOTATE_BTN_ATTR = "data-reticle-annotate-btn";
70
73
  export declare const CHAT_MIN_ATTR = "data-reticle-chat-min";
@@ -79,6 +82,7 @@ export declare const SETTINGS_PANEL_ATTR = "data-reticle-settings-panel";
79
82
  export declare const SETTINGS_CLOSE_ATTR = "data-reticle-settings-close";
80
83
  export declare const SETTING_KEY_ATTR = "data-reticle-setting";
81
84
  export declare const SETTINGS_STORAGE_KEY = "reticle-presenter-settings";
85
+ export declare const MINIMISED_STORAGE_KEY = "reticle-presenter-minimised";
82
86
  export declare const AMBIENT_GLOW_ATTR = "data-reticle-ambient-glow";
83
87
  export declare const LIVENESS_ATTR = "data-reticle-live";
84
88
  export declare const BLOCK_ATTR = "data-reticle-block";
@@ -45,6 +45,8 @@ export const DOCK_ALIGN_ATTR = 'data-reticle-dock-align';
45
45
  export const Placement = { ABOVE: 'above', BELOW: 'below' };
46
46
  export const MARKERS_BTN_ATTR = 'data-reticle-markers-btn';
47
47
  export const CLEAR_MARKS_ATTR = 'data-reticle-clear-marks';
48
+ export const COPY_MARKS_ATTR = 'data-reticle-copy-marks';
49
+ export const MARKS_ROW_ATTR = 'data-reticle-marks-row';
48
50
  export const MARK_COUNT_ATTR = 'data-reticle-mark-count';
49
51
  export const ANNOTATE_BTN_ATTR = 'data-reticle-annotate-btn';
50
52
  export const CHAT_MIN_ATTR = 'data-reticle-chat-min';
@@ -59,6 +61,7 @@ export const SETTINGS_PANEL_ATTR = 'data-reticle-settings-panel';
59
61
  export const SETTINGS_CLOSE_ATTR = 'data-reticle-settings-close';
60
62
  export const SETTING_KEY_ATTR = 'data-reticle-setting';
61
63
  export const SETTINGS_STORAGE_KEY = 'reticle-presenter-settings';
64
+ export const MINIMISED_STORAGE_KEY = 'reticle-presenter-minimised';
62
65
  export const AMBIENT_GLOW_ATTR = 'data-reticle-ambient-glow';
63
66
  export const LIVENESS_ATTR = 'data-reticle-live';
64
67
  export const BLOCK_ATTR = 'data-reticle-block';
@@ -71,6 +74,6 @@ export const HUD_POS_X_VAR = '--reticle-hud-x';
71
74
  export const HUD_POS_Y_VAR = '--reticle-hud-y';
72
75
  export const HUD_DRAG_THRESHOLD_PX = 4;
73
76
  export const HUD_DOCK_MARGIN_PX = 8;
74
- export const HUD_DRAG_IGNORE_SEL = '[data-reticle-pause], [data-reticle-annotate-btn], [data-reticle-markers-btn], [data-reticle-clear-marks], [data-reticle-end], [data-reticle-min-btn], [data-reticle-settings-btn], [data-reticle-settings-panel], [data-reticle-report-btn], [data-reticle-report-panel], [data-reticle-chat-panel], [data-reticle-chat-toggle], [data-reticle-workspace-btn], [data-reticle-workspace-menu], [data-reticle-copy], [data-reticle-export], input, textarea, select, a, .reticle-head-ctl, [data-reticle-tally], .reticle-maxhint';
77
+ export const HUD_DRAG_IGNORE_SEL = '[data-reticle-pause], [data-reticle-annotate-btn], [data-reticle-markers-btn], [data-reticle-clear-marks], [data-reticle-copy-marks], [data-reticle-end], [data-reticle-min-btn], [data-reticle-settings-btn], [data-reticle-settings-panel], [data-reticle-report-btn], [data-reticle-report-panel], [data-reticle-chat-panel], [data-reticle-chat-toggle], [data-reticle-workspace-btn], [data-reticle-workspace-menu], [data-reticle-copy], [data-reticle-export], input, textarea, select, a, .reticle-head-ctl, [data-reticle-tally], .reticle-maxhint';
75
78
  export const THROTTLED_ATTR = 'data-reticle-throttled';
76
79
  export const Z_HUD_TOOLTIP = 100;
@@ -4,6 +4,7 @@ export declare const ENDED_FADE_MS = 4000;
4
4
  export declare const CONTROLS_CSS: string;
5
5
  export declare const CONTROLS_TOOLBAR_HTML: string;
6
6
  export declare const CONTROLS_BANNER_HTML = "<div data-reticle-banner class=\"reticle-banner\">Session ended</div>";
7
+ export declare const CONTROLS_MARKS_HTML: string;
7
8
  export declare const CONTROLS_FLOWS_HTML = "<div data-reticle-flows class=\"reticle-flows\"><span class=\"reticle-flows-cap\">Replay a flow</span></div>";
8
9
  export declare const CONTROLS_FOOT_HTML: string;
9
10
  interface ControlPanelHost {
@@ -1,6 +1,6 @@
1
1
  import { HumanControlKind, PresenterTone, SessionState } from '@reticlehq/core';
2
2
  import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
3
- import { CHAT_TOGGLE_ATTR, CLEAR_MARKS_ATTR, MARKERS_BTN_ATTR } from './presenter-config.js';
3
+ import { CHAT_TOGGLE_ATTR, CLEAR_MARKS_ATTR, COPY_MARKS_ATTR, MARKERS_BTN_ATTR, MARKS_ROW_ATTR, } from './presenter-config.js';
4
4
  import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, hiIconHtml, hiToggleIconHtml, } from './icons/presenter-icons.js';
5
5
  import { getPresenterSettings } from './presenter-settings.js';
6
6
  import { mountWorkspaceSelector, workspaceRowHtml } from './presenter-workspace.js';
@@ -101,6 +101,15 @@ export const CONTROLS_CSS = `
101
101
  [data-reticle-chat-panel] .reticle-flow:active{transform:scale(.95);}
102
102
  [data-reticle-overlay][data-reticle-state="paused"] [data-reticle-hud] .reticle-tb-btn--primary{
103
103
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.16),0 0 12px rgba(255,255,255,.04);}
104
+ [data-reticle-chat-panel] .reticle-marks-row{display:flex;align-items:center;justify-content:space-between;gap:8px;
105
+ padding:7px 12px;border-top:1px solid var(--reticle-line2);pointer-events:auto;}
106
+ [data-reticle-chat-panel] .reticle-marks-row[hidden]{display:none;}
107
+ [data-reticle-chat-panel] .reticle-marks-text{color:var(--reticle-muted);font-size:11px;}
108
+ [data-reticle-chat-panel] .reticle-marks-copy{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;
109
+ border-radius:7px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);color:var(--reticle-fg);
110
+ font-family:var(--reticle-font);font-size:11px;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s;}
111
+ [data-reticle-chat-panel] .reticle-marks-copy:hover{background:var(--reticle-accent-soft);border-color:var(--reticle-accent);}
112
+ [data-reticle-chat-panel] .reticle-marks-copy[data-copied="1"]{color:#22c55e;border-color:#22c55e;}
104
113
  [data-reticle-hud] .reticle-export-msg{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
105
114
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;}
106
115
  `;
@@ -116,6 +125,7 @@ function paintPauseBtn(btn, paused) {
116
125
  const CHAT_LABEL = 'Agent chat';
117
126
  const MARKERS_LABEL = 'Hide markers';
118
127
  const CLEAR_MARKS_LABEL = 'Clear all';
128
+ const COPY_MARKS_LABEL = 'Copy for your agent';
119
129
  const TB = PRESENTER_ICON_SIZE.TOOLBAR;
120
130
  function tbWrap(btn, tip, kbd) {
121
131
  const shortcut = kbd === undefined ? '' : `<span class="reticle-tb-kbd">${kbd}</span>`;
@@ -138,6 +148,7 @@ export const CONTROLS_TOOLBAR_HTML = [
138
148
  '<span data-reticle-export-msg class="reticle-export-msg" aria-live="polite"></span>',
139
149
  ].join('');
140
150
  export const CONTROLS_BANNER_HTML = `<div data-reticle-banner class="reticle-banner">${ENDED_BANNER_TEXT}</div>`;
151
+ export const CONTROLS_MARKS_HTML = `<div ${MARKS_ROW_ATTR} class="reticle-marks-row" hidden><span data-reticle-marks-text class="reticle-marks-text"></span><button type="button" ${COPY_MARKS_ATTR} class="reticle-marks-copy" title="${COPY_MARKS_LABEL}">${hiIconHtml(PresenterIcon.COPY, PRESENTER_ICON_SIZE.HELP)}<span>${COPY_MARKS_LABEL}</span></button></div>`;
141
152
  export const CONTROLS_FLOWS_HTML = `<div data-reticle-flows class="reticle-flows"><span class="reticle-flows-cap">${FLOWS_LABEL}</span></div>`;
142
153
  export const CONTROLS_FOOT_HTML = `<div data-reticle-foot><div class="reticle-foot-stack">${workspaceRowHtml()}</div></div>`;
143
154
  function queryControlRefs(root) {
@@ -68,6 +68,10 @@ export const REPORT_CSS = `
68
68
  display:block;margin-top:2px;color:var(--reticle-c-active);opacity:.8;
69
69
  font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;cursor:help;}
70
70
  [${REPORT_PANEL_ATTR}] .reticle-report-defects-wrap{margin-top:14px;}
71
+ /* The heading and its push control on one line, so the button reads as belonging to this section
72
+ rather than floating above the list. */
73
+ [${REPORT_PANEL_ATTR}] .reticle-report-defects-head{
74
+ display:flex;align-items:center;justify-content:space-between;gap:8px;}
71
75
  [${REPORT_PANEL_ATTR}] .reticle-report-defects{
72
76
  display:flex;flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;}
73
77
  /* A left rule rather than a card per row: ten cards in a 320px panel is a wall, and the rule keeps
@@ -28,10 +28,10 @@ export function reportPanelHtml() {
28
28
  <button type="button" ${REFER_ATTR} class="reticle-report-share reticle-report-refer">${REPORT_TEXT.REFER}</button>
29
29
  </div>
30
30
  <div class="reticle-report-links">
31
- <a href="${REPORT_LINKS.DOCS}" target="_blank" rel="noreferrer noopener">Docs</a>
32
- <a href="${REPORT_LINKS.GITHUB}" target="_blank" rel="noreferrer noopener">GitHub</a>
33
- <a href="${REPORT_LINKS.SITE}" target="_blank" rel="noreferrer noopener">reticle.sh</a>
34
- <a href="${REPORT_LINKS.DISCORD}" target="_blank" rel="noreferrer noopener">Discord</a>
31
+ <a data-reticle-link="docs" href="${REPORT_LINKS.DOCS}" target="_blank" rel="noreferrer noopener">Docs</a>
32
+ <a data-reticle-link="github" href="${REPORT_LINKS.GITHUB}" target="_blank" rel="noreferrer noopener">GitHub</a>
33
+ <a data-reticle-link="site" href="${REPORT_LINKS.SITE}" target="_blank" rel="noreferrer noopener">reticle.sh</a>
34
+ <a data-reticle-link="discord" href="${REPORT_LINKS.DISCORD}" target="_blank" rel="noreferrer noopener">Discord</a>
35
35
  </div>
36
36
  </div>
37
37
  </div>`;
@@ -63,7 +63,7 @@ function defects(scope, dashboardUrl) {
63
63
  return '';
64
64
  const linked = dashboardUrl !== undefined && isSafeDashboardUrl(dashboardUrl);
65
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>`
66
+ ? `<a class="reticle-report-defect-link" data-reticle-link="defect" 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
67
  : '';
68
68
  const rows = list
69
69
  .map((d) => {
@@ -78,8 +78,9 @@ function defects(scope, dashboardUrl) {
78
78
  .join('');
79
79
  const more = !linked
80
80
  ? ''
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>`;
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>`;
81
+ : `<a class="reticle-report-defects-more" data-reticle-link="dashboard" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener">${REPORT_TEXT.DEFECTS_MORE}${scope.counts.failed > list.length ? ` (${String(scope.counts.failed)})` : ''}</a>`;
82
+ const sync = syncButtonHtml(dashboardUrl);
83
+ return `<div class="reticle-report-defects-wrap"><div class="reticle-report-defects-head"><span class="reticle-report-section">${REPORT_TEXT.DEFECTS}</span>${sync}</div><ul class="reticle-report-defects">${rows}</ul>${more}</div>`;
83
84
  }
84
85
  export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
85
86
  const c = scope.counts;
@@ -120,7 +120,8 @@ export const SETTINGS_CSS = `
120
120
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link{
121
121
  width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;
122
122
  border:none;background:transparent;color:rgba(255,255,255,.82);font:inherit;font-size:13px;
123
- padding:11px 14px;cursor:pointer;text-align:left;transition:background .12s,color .12s;}
123
+ padding:11px 14px;cursor:pointer;text-align:left;transition:background .12s,color .12s;
124
+ text-decoration:none;box-sizing:border-box;}
124
125
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link-caret{display:inline-flex;align-items:center;flex:none;opacity:.55;}
125
126
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link:hover{background:rgba(255,255,255,.04);color:#fff;}
126
127
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:-2px;}
@@ -1,3 +1,4 @@
1
+ import type { HarnessConfig } from '@reticlehq/core';
1
2
  import { SETTINGS_CSS } from './presenter-settings-styles.js';
2
3
  import type { AccountState } from '@reticlehq/core';
3
4
  import { type AccountDetails } from './presenter-account.js';
@@ -8,14 +9,14 @@ export declare const OutputDetail: {
8
9
  readonly VERBOSE: "verbose";
9
10
  };
10
11
  export type OutputDetail = (typeof OutputDetail)[keyof typeof OutputDetail];
11
- export declare const StatusThemeId: {
12
+ declare const StatusThemeId: {
12
13
  readonly SIGNAL: "signal";
13
14
  readonly TRAFFIC: "traffic";
14
15
  readonly MONO: "mono";
15
16
  readonly NEON: "neon";
16
17
  readonly EMBER: "ember";
17
18
  };
18
- export type StatusThemeId = (typeof StatusThemeId)[keyof typeof StatusThemeId];
19
+ type StatusThemeId = (typeof StatusThemeId)[keyof typeof StatusThemeId];
19
20
  interface StatusTheme {
20
21
  id: StatusThemeId;
21
22
  label: string;
@@ -23,7 +24,6 @@ interface StatusTheme {
23
24
  idle: string;
24
25
  ended: string;
25
26
  }
26
- export declare const STATUS_THEMES: readonly StatusTheme[];
27
27
  export declare function statusTheme(id: StatusThemeId): StatusTheme;
28
28
  export interface PresenterSettings {
29
29
  outputDetail: OutputDetail;
@@ -41,14 +41,15 @@ export interface PresenterSettings {
41
41
  export declare function getPresenterSettings(): PresenterSettings;
42
42
  export declare function loadPresenterSettings(): PresenterSettings;
43
43
  export declare const SETTINGS_ACCOUNT_ATTR = "data-reticle-settings-account";
44
+ export declare function paintHarnessRow(root: ParentNode, config: HarnessConfig | undefined): void;
44
45
  export declare function paintSettingsAccount(root: HTMLElement, account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
45
46
  export declare function settingsPanelHtml(): string;
46
47
  export interface SettingsHost {
47
48
  onHideUntilRestart?: () => void;
49
+ onHarness?: (enabled: boolean) => void;
48
50
  onSettingsChange?: (settings: PresenterSettings) => void;
49
51
  onBeforeOpen?: () => void;
50
52
  }
51
- export declare function applyPresenterSettings(root: HTMLElement, settings: PresenterSettings): void;
52
53
  export declare function syncPageBlocker(root: HTMLElement, settings: PresenterSettings, annotateLive: boolean): void;
53
54
  export declare function blockerHtml(): string;
54
55
  export declare class PresenterSettingsPanel {
@@ -56,6 +57,7 @@ export declare class PresenterSettingsPanel {
56
57
  constructor(host?: SettingsHost);
57
58
  contains(node: Node): boolean;
58
59
  mount(root: HTMLElement): void;
60
+ paintHarness(config: HarnessConfig | undefined): void;
59
61
  teardown(): void;
60
62
  isOpen(): boolean;
61
63
  open(): void;
@@ -4,6 +4,7 @@ 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 { DISCOVERY_CALL_URL, FOUNDER_EMAIL, FOUNDER_MAILTO } from '@reticlehq/core';
7
8
  import { accountControlHtml } from './presenter-account.js';
8
9
  export { SETTINGS_CSS };
9
10
  export const OutputDetail = {
@@ -11,14 +12,14 @@ export const OutputDetail = {
11
12
  STANDARD: 'standard',
12
13
  VERBOSE: 'verbose',
13
14
  };
14
- export const StatusThemeId = {
15
+ const StatusThemeId = {
15
16
  SIGNAL: 'signal',
16
17
  TRAFFIC: 'traffic',
17
18
  MONO: 'mono',
18
19
  NEON: 'neon',
19
20
  EMBER: 'ember',
20
21
  };
21
- export const STATUS_THEMES = [
22
+ const STATUS_THEMES = [
22
23
  {
23
24
  id: StatusThemeId.SIGNAL,
24
25
  label: 'Signal',
@@ -132,7 +133,7 @@ function settingsHelpIcon() {
132
133
  }
133
134
  function settingsLabel(text, helpTitle) {
134
135
  const help = settingsHelpIcon();
135
- return `<span class="reticle-settings-label">${text}<button type="button" class="reticle-settings-help" title="${helpTitle}" aria-label="${helpTitle}">${help}</button></span>`;
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>`;
136
137
  }
137
138
  function settingsToggleRow(key, label, helpTitle, extra = '') {
138
139
  return `<div class="reticle-settings-row" ${extra}>
@@ -149,7 +150,29 @@ function settingsCheckRow(key, label, checked) {
149
150
  }
150
151
  export const SETTINGS_ACCOUNT_ATTR = 'data-reticle-settings-account';
151
152
  const SETTINGS_ACCOUNT_ROW_ATTR = 'data-reticle-settings-account-row';
153
+ const SETTINGS_HARNESS_ROW_ATTR = 'data-reticle-settings-harness-row';
154
+ 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.';
155
+ const HARNESS_LOCKED_HELP = 'Autonomous driving runs on Reticle’s model spend, so it needs a plan or the free period. Claim it from the chat panel.';
152
156
  const ACCOUNT_HELP = 'Whether this machine is signed in to a Reticle workspace. Signing in happens in your terminal.';
157
+ export function paintHarnessRow(root, config) {
158
+ const row = root.querySelector(`[${SETTINGS_HARNESS_ROW_ATTR}]`);
159
+ if (!(row instanceof HTMLElement))
160
+ return;
161
+ if (config === undefined) {
162
+ row.hidden = true;
163
+ return;
164
+ }
165
+ row.hidden = false;
166
+ const toggle = row.querySelector(`[${SETTING_KEY_ATTR}="harnessEnabled"]`);
167
+ if (!(toggle instanceof HTMLElement))
168
+ return;
169
+ const usable = config.harnessEntitled;
170
+ toggle.setAttribute('aria-checked', config.harnessEnabled && usable ? 'true' : 'false');
171
+ toggle.setAttribute('aria-disabled', usable ? 'false' : 'true');
172
+ const help = row.querySelector('[data-reticle-help]');
173
+ if (help instanceof HTMLElement)
174
+ help.title = usable ? HARNESS_HELP : HARNESS_LOCKED_HELP;
175
+ }
153
176
  export function paintSettingsAccount(root, account, dashboardUrl, details = {}) {
154
177
  const row = root.querySelector(`[${SETTINGS_ACCOUNT_ROW_ATTR}]`);
155
178
  const slot = root.querySelector(`[${SETTINGS_ACCOUNT_ATTR}]`);
@@ -159,12 +182,19 @@ export function paintSettingsAccount(root, account, dashboardUrl, details = {})
159
182
  row.hidden = 0 === html.length;
160
183
  slot.innerHTML = html;
161
184
  }
185
+ const FEEDBACK_TEXT = {
186
+ EMAIL: 'Email the founder',
187
+ EMAIL_TITLE: `Write to ${FOUNDER_EMAIL}: a problem, a wish, anything`,
188
+ CALL: 'Book a call',
189
+ CALL_TITLE: 'Pick a time to talk it through',
190
+ };
162
191
  export function settingsPanelHtml() {
163
192
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
164
193
  const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
165
194
  const outputHelp = 'How much detail is included when you copy or export the run';
166
195
  const reactHelp = 'Include React component paths in exported run state when available';
167
196
  const hideHelp = 'Hide the Reticle HUD until you reload the page';
197
+ const harnessHelp = HARNESS_HELP;
168
198
  const tallyHelp = 'Show the pass/fail score pill in the toolbar';
169
199
  const timestampsHelp = 'Show relative timestamps on each activity-log row';
170
200
  const autoChatHelp = 'On by default. Opens the agent chat by itself when a session starts and when you expand the HUD. Off leaves the toolbar bare until you ask for the chat.';
@@ -191,6 +221,7 @@ export function settingsPanelHtml() {
191
221
  ${settingsCheckRow('blockPageInteractions', 'Block page interactions', true)}
192
222
  ${settingsCheckRow('clearOnCopy', 'Clear on copy/send', false)}
193
223
  ${settingsToggleRow('hideUntilRestart', 'Hide Until Restart', hideHelp)}
224
+ ${settingsToggleRow('harnessEnabled', 'Autonomous driving', harnessHelp, `${SETTINGS_HARNESS_ROW_ATTR} hidden`)}
194
225
  ${settingsToggleRow('reduceMotion', 'Reduce motion', motionHelp)}
195
226
  <div class="reticle-settings-section">Account</div>
196
227
  <div class="reticle-settings-row" ${SETTINGS_ACCOUNT_ROW_ATTR} hidden>
@@ -204,11 +235,13 @@ export function settingsPanelHtml() {
204
235
  <div class="reticle-settings-foot">
205
236
  <button type="button" class="reticle-settings-reset" data-reticle-settings-reset>Reset HUD position</button>
206
237
  <button type="button" class="reticle-settings-link" data-reticle-settings-mcp>Manage MCP &amp; Webhooks<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></button>
238
+ <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>
239
+ <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>
207
240
  </div>
208
241
  </div>
209
242
  </div>`;
210
243
  }
211
- export function applyPresenterSettings(root, settings) {
244
+ function applyPresenterSettings(root, settings) {
212
245
  const theme = statusTheme(settings.statusThemeId);
213
246
  root.style.setProperty('--reticle-mark-accent', theme.active);
214
247
  root.style.setProperty('--reticle-accent', theme.active);
@@ -286,7 +319,14 @@ export class PresenterSettingsPanel {
286
319
  if (!(toggle instanceof HTMLElement))
287
320
  return;
288
321
  const key = toggle.getAttribute(SETTING_KEY_ATTR);
289
- if ('reactComponents' === key) {
322
+ if ('harnessEnabled' === key) {
323
+ if ('true' === toggle.getAttribute('aria-disabled'))
324
+ return;
325
+ const next = 'true' !== toggle.getAttribute('aria-checked');
326
+ toggle.setAttribute('aria-checked', next ? 'true' : 'false');
327
+ this.#host.onHarness?.(next);
328
+ }
329
+ else if ('reactComponents' === key) {
290
330
  this.#update({ reactComponents: !activeSettings.reactComponents });
291
331
  }
292
332
  else if ('hideUntilRestart' === key) {
@@ -368,6 +408,12 @@ export class PresenterSettingsPanel {
368
408
  applyPresenterSettings(root, activeSettings);
369
409
  this.#host.onSettingsChange?.(activeSettings);
370
410
  }
411
+ paintHarness(config) {
412
+ const root = this.#panel;
413
+ if (root === undefined)
414
+ return;
415
+ paintHarnessRow(root, config);
416
+ }
371
417
  teardown() {
372
418
  this.#root = undefined;
373
419
  this.#panel = undefined;
@@ -83,6 +83,18 @@ export const SHELL_CSS = `
83
83
  right:auto;left:0;}
84
84
  [${OVERLAY}][${CHAT_ATTR}="1"] [${CHAT_PANEL}]{
85
85
  display:flex;pointer-events:auto;}
86
+ /**
87
+ * Idle, and not blocking the page: the card is not in use.
88
+ *
89
+ * The log fills the panel and sets pointer-events itself, so a click meant for a control sitting
90
+ * under the card landed on an empty transcript. Playwright named that div. The card passes the
91
+ * event through. The buttons stay clickable, which is the chrome a person still needs (#992).
92
+ */
93
+ [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}],
94
+ [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] [data-reticle-log]{
95
+ pointer-events:none;}
96
+ [${OVERLAY}][data-reticle-mode="idle"][data-reticle-block="0"] [${CHAT_PANEL}] :is(button,a,input,select,textarea){
97
+ pointer-events:auto;}
86
98
  /**
87
99
  * Minimising the chat leaves a CAPSULE, not a hole: the same glass, the same state dot, the last
88
100
  * thing the agent did - sitting directly above the toolbar capsule and reopening the panel when
@@ -234,6 +246,10 @@ export const SHELL_CSS = `
234
246
  transition:opacity .2s var(--reticle-shell-ease),transform .18s var(--reticle-shell-ease);}
235
247
  [${OVERLAY}][${MIN_ATTR}="0"] [${HUD}] .reticle-toolbar{
236
248
  display:flex;opacity:1;transform:scale(1);pointer-events:auto;}
249
+ /* The HUD block is transformed, which makes it a stacking context at level 0 inside the dock, so a
250
+ tooltip's z-index only ranked it within the block and it drew behind the chat and settings panels,
251
+ its siblings. Lift the block while a tooltip can show. */
252
+ [${HUD}]:hover,[${HUD}]:focus-within{z-index:${String(Z_HUD_TOOLTIP)};}
237
253
  [${HUD}] .reticle-toolbar-drag{cursor:grab;touch-action:none;user-select:none;}
238
254
  [${HUD}] .reticle-toolbar-drag.reticle-drag-handle--dragging{cursor:grabbing;}
239
255
  [${HUD}] .reticle-toolbar-actions{
@@ -1,6 +1,8 @@
1
+ import type { HarnessConfig } from '@reticlehq/core';
1
2
  import { PresenterReport } from './presenter-report.js';
2
3
  import type { AccountState } from '@reticlehq/core';
3
4
  import { type AccountDetails } from './presenter-account.js';
5
+ import { type OfferState } from './carousel/offer-card.js';
4
6
  import { type SettingsHost } from './presenter-settings.js';
5
7
  interface HudShellCallbacks {
6
8
  onChatOpen?: () => void;
@@ -13,7 +15,9 @@ interface HudShellCallbacks {
13
15
  }
14
16
  export declare class HudShell {
15
17
  #private;
18
+ paintHarness(config: HarnessConfig | undefined): void;
16
19
  paintAccount(account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
20
+ paintOffer(offer: OfferState | undefined): void;
17
21
  constructor(callbacks?: HudShellCallbacks);
18
22
  static dockHtml(actStripHtml: string, bannerHtml: string, logAttr: string, flowsHtml: string, footHtml: string): string;
19
23
  get report(): PresenterReport;
@@ -31,4 +35,7 @@ export declare class HudShell {
31
35
  showChat(): void;
32
36
  pulseFab(active: boolean): void;
33
37
  }
38
+ export declare function wasMinimised(): boolean;
39
+ export declare function rememberMinimised(minimised: boolean): void;
40
+ export declare function shouldAutoOpenChat(autoOpenChat: boolean): boolean;
34
41
  export {};