@reticlehq/browser 3.2.0 → 3.4.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/actions-dom.d.ts +6 -5
  2. package/dist/actions/actions-dom.js +5 -4
  3. package/dist/actions/actions-press.d.ts +3 -3
  4. package/dist/actions/actions-press.js +0 -3
  5. package/dist/actions/actions.d.ts +0 -1
  6. package/dist/actions/actions.js +17 -13
  7. package/dist/actions/check-state.d.ts +5 -0
  8. package/dist/actions/check-state.js +24 -0
  9. package/dist/actions/danger-context.d.ts +3 -2
  10. package/dist/actions/danger-context.js +2 -1
  11. package/dist/actions/value-input.d.ts +2 -1
  12. package/dist/actions/value-input.js +15 -6
  13. package/dist/connect-options.d.ts +1 -0
  14. package/dist/dom/a11y.js +43 -3
  15. package/dist/dom/addressing/source.d.ts +2 -1
  16. package/dist/dom/dom-ignore.d.ts +1 -0
  17. package/dist/dom/dom-ignore.js +13 -0
  18. package/dist/dom/query.js +6 -4
  19. package/dist/dom/realm.d.ts +6 -0
  20. package/dist/dom/realm.js +5 -0
  21. package/dist/dom/snapshot.d.ts +1 -1
  22. package/dist/dom/snapshot.js +8 -2
  23. package/dist/observers/console.js +38 -13
  24. package/dist/observers/dom.js +3 -1
  25. package/dist/observers/download.js +3 -2
  26. package/dist/observers/error-repeats.d.ts +18 -0
  27. package/dist/observers/error-repeats.js +41 -0
  28. package/dist/observers/field.d.ts +2 -0
  29. package/dist/observers/field.js +86 -0
  30. package/dist/observers/frames.js +3 -4
  31. package/dist/observers/install-all.js +2 -0
  32. package/dist/observers/net-detail/network-body.d.ts +2 -0
  33. package/dist/observers/net-detail/network-body.js +24 -7
  34. package/dist/observers/network.js +14 -1
  35. package/dist/observers/route.js +4 -4
  36. package/dist/observers/state.js +21 -8
  37. package/dist/presenter/carousel/carousel.d.ts +13 -0
  38. package/dist/presenter/carousel/carousel.js +126 -0
  39. package/dist/presenter/carousel/offer-card.d.ts +21 -0
  40. package/dist/presenter/carousel/offer-card.js +55 -0
  41. package/dist/presenter/carousel/panel-slides.d.ts +7 -0
  42. package/dist/presenter/carousel/panel-slides.js +10 -0
  43. package/dist/presenter/carousel/talk-card.d.ts +8 -0
  44. package/dist/presenter/carousel/talk-card.js +20 -0
  45. package/dist/presenter/chrome/presenter-log.d.ts +2 -1
  46. package/dist/presenter/chrome/presenter-log.js +14 -3
  47. package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -1
  48. package/dist/presenter/chrome/presenter-report-copy.js +1 -1
  49. package/dist/presenter/hud-telemetry.d.ts +3 -0
  50. package/dist/presenter/hud-telemetry.js +87 -0
  51. package/dist/presenter/presenter-account.js +1 -1
  52. package/dist/presenter/presenter-config.d.ts +5 -1
  53. package/dist/presenter/presenter-config.js +4 -1
  54. package/dist/presenter/presenter-controls.d.ts +1 -0
  55. package/dist/presenter/presenter-controls.js +12 -1
  56. package/dist/presenter/presenter-report.js +6 -6
  57. package/dist/presenter/presenter-settings-styles.js +2 -1
  58. package/dist/presenter/presenter-settings.d.ts +2 -4
  59. package/dist/presenter/presenter-settings.js +13 -4
  60. package/dist/presenter/presenter-shell-styles.js +4 -0
  61. package/dist/presenter/presenter-shell.d.ts +4 -1
  62. package/dist/presenter/presenter-shell.js +49 -11
  63. package/dist/presenter/presenter-styles.js +5 -1
  64. package/dist/presenter/presenter-workspace.d.ts +0 -7
  65. package/dist/presenter/presenter-workspace.js +7 -7
  66. package/dist/presenter/presenter.js +43 -10
  67. package/dist/presenter/tour/tour.d.ts +3 -0
  68. package/dist/presenter/tour/tour.js +5 -0
  69. package/dist/recorder/recorder.js +25 -22
  70. package/dist/registry/store-adapters.js +32 -2
  71. package/dist/registry/stores/adapters.js +8 -2
  72. package/dist/reticle-inject.d.ts +2 -0
  73. package/dist/reticle-inject.js +1337 -0
  74. package/dist/reticle.js +6 -0
  75. package/dist/review/annotator.d.ts +3 -1
  76. package/dist/review/annotator.js +37 -40
  77. package/dist/review/marks-for-agent.d.ts +9 -0
  78. package/dist/review/marks-for-agent.js +12 -0
  79. package/dist/util/captured-method.d.ts +0 -1
  80. package/dist/util/captured-method.js +1 -1
  81. package/package.json +15 -11
  82. package/dist/presenter/presenter-offer.d.ts +0 -25
  83. package/dist/presenter/presenter-offer.js +0 -87
@@ -0,0 +1,20 @@
1
+ import { DISCOVERY_CALL_URL } from '@reticlehq/core';
2
+ export const TALK_BOOK_ATTR = 'data-reticle-talk-book';
3
+ export const TALK_TEXT = {
4
+ HEADLINE: 'Talk to Founders',
5
+ BODY: 'Tell us where Reticle got in your way.',
6
+ BOOK: 'Book a call',
7
+ };
8
+ const escape = (text) => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
9
+ export function talkCardHtml() {
10
+ return `<div class="reticle-talk">
11
+ <span class="reticle-talk-title">${escape(TALK_TEXT.HEADLINE)}</span>
12
+ <p class="reticle-talk-body">${escape(TALK_TEXT.BODY)}</p>
13
+ <a ${TALK_BOOK_ATTR} class="reticle-talk-book" href="${escape(DISCOVERY_CALL_URL)}" target="_blank" rel="noopener noreferrer">${escape(TALK_TEXT.BOOK)}</a>
14
+ </div>`;
15
+ }
16
+ export const TALK_CSS = `
17
+ .reticle-talk-title{display:block;font-weight:600;font-size:12px;padding-right:18px;}
18
+ .reticle-talk-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}
19
+ .reticle-talk-book{display:inline-block;font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}
20
+ `;
@@ -6,11 +6,12 @@ export declare const CHIP_LABEL: Record<PresenterMode, string>;
6
6
  export declare const DATA_RETICLE_LOG = "data-reticle-log";
7
7
  export declare const LOG_TIME_ATTR = "data-reticle-log-time";
8
8
  export declare const LOG_CSS: string;
9
- export declare function scrollLogToLatest(container: HTMLElement): void;
10
9
  export declare function settleLogAtLatest(container: HTMLElement): void;
11
10
  export interface LogHandle {
12
11
  result(r: LogResult): void;
13
12
  }
13
+ export declare function trimLogRows(container: HTMLElement, max: number): void;
14
+ export declare function clearLogRows(container: HTMLElement): void;
14
15
  export declare function clampLogMax(n: number | undefined): number;
15
16
  export declare function humanDuration(ms: number): string;
16
17
  export declare function formatElapsed(ms: number): string;
@@ -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) {
@@ -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,7 +78,7 @@ 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>`;
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
82
  const sync = syncButtonHtml(dashboardUrl);
83
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>`;
84
84
  }
@@ -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;}
@@ -9,14 +9,14 @@ export declare const OutputDetail: {
9
9
  readonly VERBOSE: "verbose";
10
10
  };
11
11
  export type OutputDetail = (typeof OutputDetail)[keyof typeof OutputDetail];
12
- export declare const StatusThemeId: {
12
+ declare const StatusThemeId: {
13
13
  readonly SIGNAL: "signal";
14
14
  readonly TRAFFIC: "traffic";
15
15
  readonly MONO: "mono";
16
16
  readonly NEON: "neon";
17
17
  readonly EMBER: "ember";
18
18
  };
19
- export type StatusThemeId = (typeof StatusThemeId)[keyof typeof StatusThemeId];
19
+ type StatusThemeId = (typeof StatusThemeId)[keyof typeof StatusThemeId];
20
20
  interface StatusTheme {
21
21
  id: StatusThemeId;
22
22
  label: string;
@@ -24,7 +24,6 @@ interface StatusTheme {
24
24
  idle: string;
25
25
  ended: string;
26
26
  }
27
- export declare const STATUS_THEMES: readonly StatusTheme[];
28
27
  export declare function statusTheme(id: StatusThemeId): StatusTheme;
29
28
  export interface PresenterSettings {
30
29
  outputDetail: OutputDetail;
@@ -51,7 +50,6 @@ export interface SettingsHost {
51
50
  onSettingsChange?: (settings: PresenterSettings) => void;
52
51
  onBeforeOpen?: () => void;
53
52
  }
54
- export declare function applyPresenterSettings(root: HTMLElement, settings: PresenterSettings): void;
55
53
  export declare function syncPageBlocker(root: HTMLElement, settings: PresenterSettings, annotateLive: boolean): void;
56
54
  export declare function blockerHtml(): string;
57
55
  export declare class PresenterSettingsPanel {
@@ -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}>
@@ -181,6 +182,12 @@ export function paintSettingsAccount(root, account, dashboardUrl, details = {})
181
182
  row.hidden = 0 === html.length;
182
183
  slot.innerHTML = html;
183
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
+ };
184
191
  export function settingsPanelHtml() {
185
192
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
186
193
  const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
@@ -228,11 +235,13 @@ export function settingsPanelHtml() {
228
235
  <div class="reticle-settings-foot">
229
236
  <button type="button" class="reticle-settings-reset" data-reticle-settings-reset>Reset HUD position</button>
230
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>
231
240
  </div>
232
241
  </div>
233
242
  </div>`;
234
243
  }
235
- export function applyPresenterSettings(root, settings) {
244
+ function applyPresenterSettings(root, settings) {
236
245
  const theme = statusTheme(settings.statusThemeId);
237
246
  root.style.setProperty('--reticle-mark-accent', theme.active);
238
247
  root.style.setProperty('--reticle-accent', theme.active);
@@ -246,6 +246,10 @@ export const SHELL_CSS = `
246
246
  transition:opacity .2s var(--reticle-shell-ease),transform .18s var(--reticle-shell-ease);}
247
247
  [${OVERLAY}][${MIN_ATTR}="0"] [${HUD}] .reticle-toolbar{
248
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)};}
249
253
  [${HUD}] .reticle-toolbar-drag{cursor:grab;touch-action:none;user-select:none;}
250
254
  [${HUD}] .reticle-toolbar-drag.reticle-drag-handle--dragging{cursor:grabbing;}
251
255
  [${HUD}] .reticle-toolbar-actions{
@@ -2,7 +2,7 @@ import type { HarnessConfig } from '@reticlehq/core';
2
2
  import { PresenterReport } from './presenter-report.js';
3
3
  import type { AccountState } from '@reticlehq/core';
4
4
  import { type AccountDetails } from './presenter-account.js';
5
- import { type OfferState } from './presenter-offer.js';
5
+ import { type OfferState } from './carousel/offer-card.js';
6
6
  import { type SettingsHost } from './presenter-settings.js';
7
7
  interface HudShellCallbacks {
8
8
  onChatOpen?: () => void;
@@ -35,4 +35,7 @@ export declare class HudShell {
35
35
  showChat(): void;
36
36
  pulseFab(active: boolean): void;
37
37
  }
38
+ export declare function wasMinimised(): boolean;
39
+ export declare function rememberMinimised(minimised: boolean): void;
40
+ export declare function shouldAutoOpenChat(autoOpenChat: boolean): boolean;
38
41
  export {};
@@ -1,11 +1,14 @@
1
+ import { appModalOpen } from '../dom/dom-ignore.js';
1
2
  import { PresenterReport, reportPanelHtml } from './presenter-report.js';
2
3
  import { paintToolbarAccount, TOOLBAR_ACCOUNT_ATTR } from './presenter-workspace.js';
3
4
  import { mountAccountControl } from './presenter-account.js';
4
5
  import { paintSettingsAccount } from './presenter-settings.js';
5
- 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, } from './presenter-config.js';
6
- import { OFFER_SLOT_ATTR, paintOffer } from './presenter-offer.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';
7
+ import { offerDismissed } from './carousel/offer-card.js';
8
+ import { paintCarousel } from './carousel/carousel.js';
9
+ import { panelSlides } from './carousel/panel-slides.js';
7
10
  import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './chrome/presenter-brand.js';
8
- import { settleLogAtLatest } from './chrome/presenter-log.js';
11
+ import { DATA_RETICLE_LOG, settleLogAtLatest } from './chrome/presenter-log.js';
9
12
  import { installHudDragHandles, installHudPositionGuards } from './presenter-drag.js';
10
13
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
11
14
  import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
@@ -59,9 +62,22 @@ export class HudShell {
59
62
  #pushedOffer;
60
63
  paintOffer(offer) {
61
64
  this.#pushedOffer = offer;
62
- if (this.#root === undefined)
65
+ this.#paintCarousel();
66
+ }
67
+ #paintCarousel() {
68
+ const log = this.#root?.querySelector(`[${DATA_RETICLE_LOG}]`);
69
+ if (!(log instanceof HTMLElement))
63
70
  return;
64
- paintOffer(this.#root, offer, this.#storage());
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
+ }
65
81
  }
66
82
  #storage() {
67
83
  try {
@@ -97,7 +113,6 @@ export class HudShell {
97
113
  ${actStripHtml}
98
114
  <span class="reticle-tally" data-reticle-tally hidden></span>
99
115
  ${bannerHtml}
100
- <div ${OFFER_SLOT_ATTR}></div>
101
116
  <div class="${HUD_LOG_WELL_CLASS}"><div ${logAttr}></div></div>
102
117
  ${flowsHtml}
103
118
  ${footHtml}
@@ -248,8 +263,7 @@ export class HudShell {
248
263
  const pushed = this.#pushedAccount;
249
264
  this.paintAccount(pushed.account, pushed.dashboardUrl, pushed.details);
250
265
  }
251
- if (this.#pushedOffer !== undefined)
252
- this.paintOffer(this.#pushedOffer);
266
+ this.#paintCarousel();
253
267
  }
254
268
  teardown() {
255
269
  this.#accountTeardown?.();
@@ -283,6 +297,7 @@ export class HudShell {
283
297
  return;
284
298
  this.#lockTransition();
285
299
  this.#root.setAttribute(MIN_ATTR, '0');
300
+ rememberMinimised(false);
286
301
  if (this.#fab !== undefined)
287
302
  this.#fab.setAttribute('aria-expanded', 'true');
288
303
  this.#callbacks.onExpand?.();
@@ -297,6 +312,7 @@ export class HudShell {
297
312
  this.closeChat();
298
313
  this.#settings.close();
299
314
  this.#root.setAttribute(MIN_ATTR, '1');
315
+ rememberMinimised(true);
300
316
  if (this.#fab !== undefined)
301
317
  this.#fab.setAttribute('aria-expanded', 'false');
302
318
  this.#callbacks.onCollapse?.();
@@ -367,6 +383,10 @@ export class HudShell {
367
383
  #onKeyDown = (e) => {
368
384
  if ('Escape' !== e.key || this.#root === undefined)
369
385
  return;
386
+ if (e.defaultPrevented)
387
+ return;
388
+ if (appModalOpen(document))
389
+ return;
370
390
  const target = e.target;
371
391
  if (target instanceof HTMLElement &&
372
392
  ('INPUT' === target.tagName ||
@@ -375,18 +395,36 @@ export class HudShell {
375
395
  return;
376
396
  }
377
397
  if (this.isChatOpen()) {
378
- e.preventDefault();
379
398
  this.closeChat();
380
399
  return;
381
400
  }
382
401
  if (this.#settings.isOpen()) {
383
- e.preventDefault();
384
402
  this.#settings.close();
385
403
  return;
386
404
  }
387
405
  if (!this.isCollapsed()) {
388
- e.preventDefault();
389
406
  this.collapse();
390
407
  }
391
408
  };
392
409
  }
410
+ export function wasMinimised() {
411
+ try {
412
+ return '1' === globalThis.sessionStorage.getItem(MINIMISED_STORAGE_KEY);
413
+ }
414
+ catch {
415
+ return false;
416
+ }
417
+ }
418
+ export function rememberMinimised(minimised) {
419
+ try {
420
+ if (minimised)
421
+ globalThis.sessionStorage.setItem(MINIMISED_STORAGE_KEY, '1');
422
+ else
423
+ globalThis.sessionStorage.removeItem(MINIMISED_STORAGE_KEY);
424
+ }
425
+ catch {
426
+ }
427
+ }
428
+ export function shouldAutoOpenChat(autoOpenChat) {
429
+ return autoOpenChat && !wasMinimised();
430
+ }
@@ -6,7 +6,9 @@ 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 './presenter-offer.js';
9
+ import { OFFER_CSS } from './carousel/offer-card.js';
10
+ import { TALK_CSS } from './carousel/talk-card.js';
11
+ import { CAROUSEL_CSS } from './carousel/carousel.js';
10
12
  export { Z_OVERLAY };
11
13
  export const PRESENTER_CSS = `
12
14
  [data-reticle-overlay]{position:fixed;inset:0;pointer-events:none;z-index:${String(Z_OVERLAY)};}
@@ -65,5 +67,7 @@ ${SETTINGS_CSS}
65
67
  ${REPORT_CSS}
66
68
  ${ACCOUNT_CSS}
67
69
  ${OFFER_CSS}
70
+ ${TALK_CSS}
71
+ ${CAROUSEL_CSS}
68
72
  ${LOG_CSS}
69
73
  ${CONTROLS_CSS}`;
@@ -1,14 +1,7 @@
1
1
  import type { AccountState } from '@reticlehq/core';
2
2
  import { type AccountDetails } from './presenter-account.js';
3
3
  export declare const TOOLBAR_ACCOUNT_ATTR = "data-reticle-toolbar-account";
4
- export declare function readWorkspaceRoot(): string | undefined;
5
4
  export declare function workspaceFolderLabel(root: string): string;
6
- export declare function readProjectIdFromUrl(): string | undefined;
7
- export declare function workspaceSummary(): {
8
- folder: string;
9
- root?: string;
10
- projectId?: string;
11
- };
12
5
  export declare function workspaceRowHtml(): string;
13
6
  export declare function paintWorkspace(root: HTMLElement): void;
14
7
  export declare function mountWorkspaceSelector(root: HTMLElement): () => void;