@reticlehq/browser 2.14.0 → 3.2.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 (133) hide show
  1. package/dist/actions/actions-dom.d.ts +9 -0
  2. package/dist/actions/actions-dom.js +81 -28
  3. package/dist/actions/actions-press.d.ts +4 -0
  4. package/dist/actions/actions-press.js +46 -0
  5. package/dist/actions/actions.d.ts +1 -0
  6. package/dist/actions/actions.js +50 -13
  7. package/dist/actions/appeared-text.js +6 -0
  8. package/dist/actions/scroll.d.ts +8 -1
  9. package/dist/actions/scroll.js +28 -10
  10. package/dist/commands/commands.js +10 -3
  11. package/dist/dom/a11y.js +2 -2
  12. package/dist/dom/desktop-capture.js +1 -1
  13. package/dist/dom/dom-ignore.js +1 -1
  14. package/dist/dom/occlusion.js +31 -5
  15. package/dist/dom/paint-context.d.ts +6 -0
  16. package/dist/dom/paint-context.js +38 -0
  17. package/dist/dom/query.js +24 -7
  18. package/dist/dom/snapshot.js +1 -1
  19. package/dist/edit-epoch.d.ts +1 -1
  20. package/dist/edit-epoch.js +1 -1
  21. package/dist/index.d.ts +4 -5
  22. package/dist/index.js +3 -5
  23. package/dist/observers/animation.js +1 -1
  24. package/dist/observers/blind-spots.js +1 -1
  25. package/dist/observers/departure.js +16 -8
  26. package/dist/observers/dialogs.js +1 -1
  27. package/dist/observers/dom.js +28 -8
  28. package/dist/observers/download.js +1 -1
  29. package/dist/observers/health.js +1 -1
  30. package/dist/observers/{network-body.js → net-detail/network-body.js} +2 -2
  31. package/dist/observers/network-stream.js +2 -2
  32. package/dist/observers/network.d.ts +1 -1
  33. package/dist/observers/network.js +51 -10
  34. package/dist/observers/scroll.js +2 -2
  35. package/dist/presenter/chrome/layers.d.ts +2 -0
  36. package/dist/presenter/chrome/layers.js +2 -0
  37. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  38. package/dist/presenter/chrome/log-kinds.js +7 -0
  39. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -1
  40. package/dist/presenter/{presenter-log.d.ts → chrome/presenter-log.d.ts} +3 -12
  41. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +4 -9
  42. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  43. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  44. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -1
  45. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -0
  46. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -0
  47. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -0
  48. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -0
  49. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  50. package/dist/presenter/presenter-account-styles.js +99 -0
  51. package/dist/presenter/presenter-account.d.ts +40 -0
  52. package/dist/presenter/presenter-account.js +178 -0
  53. package/dist/presenter/presenter-config.d.ts +9 -2
  54. package/dist/presenter/presenter-config.js +3 -1
  55. package/dist/presenter/presenter-controls.d.ts +1 -6
  56. package/dist/presenter/presenter-controls.js +7 -2
  57. package/dist/presenter/presenter-dock-layout.js +1 -1
  58. package/dist/presenter/presenter-drag.d.ts +1 -19
  59. package/dist/presenter/presenter-drag.js +3 -50
  60. package/dist/presenter/presenter-glow.js +1 -1
  61. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  62. package/dist/presenter/presenter-hud-position.js +50 -0
  63. package/dist/presenter/presenter-offer.d.ts +25 -0
  64. package/dist/presenter/presenter-offer.js +87 -0
  65. package/dist/presenter/presenter-report-styles.js +20 -1
  66. package/dist/presenter/presenter-report.d.ts +2 -1
  67. package/dist/presenter/presenter-report.js +31 -23
  68. package/dist/presenter/presenter-run-state.d.ts +1 -1
  69. package/dist/presenter/presenter-run-state.js +1 -1
  70. package/dist/presenter/presenter-settings-styles.js +2 -1
  71. package/dist/presenter/presenter-settings.d.ts +8 -0
  72. package/dist/presenter/presenter-settings.js +58 -3
  73. package/dist/presenter/presenter-shell-styles.js +33 -15
  74. package/dist/presenter/presenter-shell.d.ts +8 -0
  75. package/dist/presenter/presenter-shell.js +47 -4
  76. package/dist/presenter/presenter-styles.d.ts +2 -1
  77. package/dist/presenter/presenter-styles.js +8 -3
  78. package/dist/presenter/presenter-workspace.d.ts +4 -0
  79. package/dist/presenter/presenter-workspace.js +29 -2
  80. package/dist/presenter/presenter.d.ts +3 -3
  81. package/dist/presenter/presenter.js +28 -14
  82. package/dist/presenter/tour/tour-view.d.ts +33 -0
  83. package/dist/presenter/tour/tour-view.js +192 -0
  84. package/dist/presenter/tour/tour.d.ts +25 -0
  85. package/dist/presenter/tour/tour.js +264 -0
  86. package/dist/recorder/recorder.js +6 -0
  87. package/dist/registry/declared-channels.d.ts +2 -0
  88. package/dist/registry/declared-channels.js +14 -0
  89. package/dist/registry/declared-platform.d.ts +4 -0
  90. package/dist/registry/declared-platform.js +34 -0
  91. package/dist/registry/emitter.js +1 -1
  92. package/dist/registry/store-adapters.js +1 -1
  93. package/dist/registry/stores.js +1 -1
  94. package/dist/reticle-presenter-helpers.js +10 -6
  95. package/dist/reticle-singleton.d.ts +2 -0
  96. package/dist/reticle-singleton.js +3 -0
  97. package/dist/reticle.js +99 -35
  98. package/dist/review/annotator-styles.js +1 -1
  99. package/dist/review/annotator.js +2 -2
  100. package/dist/review/mark-anchor.js +3 -3
  101. package/dist/transport/transport.js +2 -2
  102. package/package.json +5 -4
  103. package/dist/presenter/presenter-test-helpers.d.ts +0 -14
  104. package/dist/presenter/presenter-test-helpers.js +0 -39
  105. /package/dist/actions/{synthetic-input.d.ts → synthetic/synthetic-input.d.ts} +0 -0
  106. /package/dist/actions/{synthetic-input.js → synthetic/synthetic-input.js} +0 -0
  107. /package/dist/dom/{auto-anchor.d.ts → addressing/auto-anchor.d.ts} +0 -0
  108. /package/dist/dom/{auto-anchor.js → addressing/auto-anchor.js} +0 -0
  109. /package/dist/dom/{refs.d.ts → addressing/refs.d.ts} +0 -0
  110. /package/dist/dom/{refs.js → addressing/refs.js} +0 -0
  111. /package/dist/dom/{source.d.ts → addressing/source.d.ts} +0 -0
  112. /package/dist/dom/{source.js → addressing/source.js} +0 -0
  113. /package/dist/observers/{network-body.d.ts → net-detail/network-body.d.ts} +0 -0
  114. /package/dist/observers/{network-redact.d.ts → net-detail/network-redact.d.ts} +0 -0
  115. /package/dist/observers/{network-redact.js → net-detail/network-redact.js} +0 -0
  116. /package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -0
  117. /package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -0
  118. /package/dist/presenter/{presenter-brand.d.ts → chrome/presenter-brand.d.ts} +0 -0
  119. /package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -0
  120. /package/dist/presenter/{presenter-effects.d.ts → chrome/presenter-effects.d.ts} +0 -0
  121. /package/dist/presenter/{presenter-hud-chrome.d.ts → chrome/presenter-hud-chrome.d.ts} +0 -0
  122. /package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -0
  123. /package/dist/presenter/{presenter-report-copy.d.ts → chrome/presenter-report-copy.d.ts} +0 -0
  124. /package/dist/presenter/{presenter-report-copy.js → chrome/presenter-report-copy.js} +0 -0
  125. /package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -0
  126. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  127. /package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -0
  128. /package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -0
  129. /package/dist/registry/{adapters.js → stores/adapters.js} +0 -0
  130. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
  131. /package/dist/timers/{native-console.js → console/native-console.js} +0 -0
  132. /package/dist/timers/{native-timers.d.ts → native/native-timers.d.ts} +0 -0
  133. /package/dist/timers/{native-timers.js → native/native-timers.js} +0 -0
@@ -0,0 +1,87 @@
1
+ export const OFFER_ATTR = 'data-reticle-offer';
2
+ export const OFFER_SLOT_ATTR = 'data-reticle-offer-slot';
3
+ export const OFFER_CLAIM_ATTR = 'data-reticle-offer-claim';
4
+ export const OFFER_DISMISS_ATTR = 'data-reticle-offer-dismiss';
5
+ export const OFFER_DISMISSED_KEY = 'reticle.harnessOffer.dismissed';
6
+ export const OFFER_TEXT = {
7
+ HEADLINE: 'Reticle drives your app for you',
8
+ BODY: 'The harness explores your app, proves what works, and saves every journey as a test that replays for free. Built on Jev — no model API key needed.',
9
+ FREE: 'Free for 3 months',
10
+ CLAIM: 'Claim now',
11
+ DISMISS: 'Not now',
12
+ claimed: (days) => `Harness active — ${String(days)} days left`,
13
+ ending: (days) => 1 >= days
14
+ ? 'Harness free period ends today'
15
+ : `Harness free period ends in ${String(days)} days`,
16
+ };
17
+ const ENDING_SOON_DAYS = 14;
18
+ export function offerDismissed(storage) {
19
+ try {
20
+ return '1' === storage?.getItem(OFFER_DISMISSED_KEY);
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ export function dismissOffer(storage) {
27
+ try {
28
+ storage?.setItem(OFFER_DISMISSED_KEY, '1');
29
+ }
30
+ catch {
31
+ }
32
+ }
33
+ const escape = (text) => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
34
+ export function offerHtml(offer, dismissed) {
35
+ if (offer === undefined)
36
+ return '';
37
+ if (offer.claimed) {
38
+ const days = offer.daysRemaining;
39
+ if (days === undefined || 0 > days)
40
+ return '';
41
+ const text = ENDING_SOON_DAYS >= days ? OFFER_TEXT.ending(days) : OFFER_TEXT.claimed(days);
42
+ return `<div ${OFFER_ATTR} class="reticle-offer reticle-offer-claimed">${escape(text)}</div>`;
43
+ }
44
+ if (false === offer.eligible)
45
+ return '';
46
+ if (dismissed)
47
+ return '';
48
+ const url = offer.claimUrl;
49
+ if (url === undefined || 0 === url.length)
50
+ return '';
51
+ return `<div ${OFFER_ATTR} class="reticle-offer" role="note">
52
+ <div class="reticle-offer-head">
53
+ <span class="reticle-offer-title">${escape(OFFER_TEXT.HEADLINE)}</span>
54
+ <span class="reticle-offer-free">${escape(OFFER_TEXT.FREE)}</span>
55
+ </div>
56
+ <p class="reticle-offer-body">${escape(OFFER_TEXT.BODY)}</p>
57
+ <div class="reticle-offer-actions">
58
+ <a ${OFFER_CLAIM_ATTR} class="reticle-offer-claim" href="${escape(url)}" target="_blank" rel="noopener noreferrer">${escape(OFFER_TEXT.CLAIM)}</a>
59
+ <button type="button" ${OFFER_DISMISS_ATTR} class="reticle-offer-dismiss">${escape(OFFER_TEXT.DISMISS)}</button>
60
+ </div>
61
+ </div>`;
62
+ }
63
+ export function paintOffer(root, offer, storage) {
64
+ const slot = root.querySelector(`[${OFFER_SLOT_ATTR}]`);
65
+ if (!(slot instanceof HTMLElement))
66
+ return;
67
+ slot.innerHTML = offerHtml(offer, offerDismissed(storage));
68
+ const dismiss = slot.querySelector(`[${OFFER_DISMISS_ATTR}]`);
69
+ dismiss?.addEventListener('click', (event) => {
70
+ event.preventDefault();
71
+ event.stopPropagation();
72
+ dismissOffer(storage);
73
+ slot.innerHTML = '';
74
+ });
75
+ }
76
+ export const OFFER_CSS = `
77
+ .reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}
78
+ .reticle-offer-head{display:flex;align-items:center;justify-content:space-between;gap:8px;}
79
+ .reticle-offer-title{font-weight:600;font-size:12px;}
80
+ .reticle-offer-free{font-size:10px;padding:2px 6px;border-radius:999px;background:var(--reticle-accent,#4f7cff);color:#fff;white-space:nowrap;}
81
+ .reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}
82
+ .reticle-offer-actions{display:flex;align-items:center;gap:8px;}
83
+ .reticle-offer-claim{font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}
84
+ .reticle-offer-dismiss{font-size:11px;padding:4px 8px;border-radius:6px;background:none;border:0;color:inherit;opacity:.6;cursor:pointer;}
85
+ .reticle-offer-dismiss:hover{opacity:1;}
86
+ .reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}
87
+ `;
@@ -1,4 +1,4 @@
1
- import { HUD_DROP_SHADOW } from './presenter-hud-chrome.js';
1
+ import { HUD_DROP_SHADOW } from './chrome/presenter-hud-chrome.js';
2
2
  import { DOCK_ALIGN_ATTR, DOCK_ATTR, REPORT_ATTR, REPORT_PANEL_ATTR } from './presenter-config.js';
3
3
  const OVERLAY = 'data-reticle-overlay';
4
4
  export const REPORT_CSS = `
@@ -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
@@ -123,4 +127,19 @@ export const REPORT_CSS = `
123
127
  [${REPORT_PANEL_ATTR}] .reticle-report-links a{
124
128
  color:var(--reticle-faint);font-size:10.5px;text-decoration:none;}
125
129
  [${REPORT_PANEL_ATTR}] .reticle-report-links a:hover{color:var(--reticle-fg);text-decoration:underline;}
130
+
131
+ /* Who this record belongs to, and the push-now control. Tokens only — no literal colours. */
132
+ [${REPORT_PANEL_ATTR}] .reticle-report-identity{
133
+ display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:10px 14px 0;min-height:0;}
134
+ [${REPORT_PANEL_ATTR}] .reticle-report-identity:empty{display:none;}
135
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now{
136
+ display:inline-flex;align-items:center;justify-content:center;
137
+ border:none;background:transparent;color:var(--reticle-faint);cursor:pointer;line-height:0;padding:2px;}
138
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now:hover{color:var(--reticle-fg);}
139
+ /* The one moment this control has an opinion: it says it is going, not that it has arrived. */
140
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now[data-syncing="1"]{
141
+ color:var(--reticle-c-active);animation:reticle-sync-spin 900ms linear infinite;}
142
+ @keyframes reticle-sync-spin{to{transform:rotate(360deg);}}
143
+ @media (prefers-reduced-motion:reduce){
144
+ [${REPORT_PANEL_ATTR}] .reticle-sync-now[data-syncing="1"]{animation:none;}}
126
145
  `;
@@ -1,8 +1,9 @@
1
1
  import { type AccountState, type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
2
2
  export declare function reportPanelHtml(): string;
3
- export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState): string;
3
+ export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState, projectName?: string): string;
4
4
  interface ReportHost {
5
5
  onBeforeOpen?: () => void;
6
+ onSyncNow?: () => void;
6
7
  }
7
8
  export declare class PresenterReport {
8
9
  #private;
@@ -1,8 +1,10 @@
1
1
  import { IMPACT_DEFECT_LIMIT, } from '@reticlehq/core';
2
- import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './presenter-icons.js';
3
- import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
2
+ import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './icons/presenter-icons.js';
3
+ import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
4
+ import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
5
+ import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, SYNC_BTN_ATTR, accountControlHtml, syncButtonHtml, } from './presenter-account.js';
4
6
  import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
5
- import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './presenter-report-copy.js';
7
+ import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './chrome/presenter-report-copy.js';
6
8
  const SCOPE_ATTR = 'data-reticle-report-scope';
7
9
  const SHARE_X_ATTR = 'data-reticle-share-x';
8
10
  const SHARE_IN_ATTR = 'data-reticle-share-in';
@@ -55,22 +57,6 @@ function chart(scope) {
55
57
  .join('');
56
58
  return `<div class="reticle-report-chart-wrap"><span class="reticle-report-section">${REPORT_TEXT.CHART}</span><div class="reticle-report-chart">${bars}</div></div>`;
57
59
  }
58
- function esc(value) {
59
- return value
60
- .replace(/&/g, '&amp;')
61
- .replace(/</g, '&lt;')
62
- .replace(/>/g, '&gt;')
63
- .replace(/"/g, '&quot;');
64
- }
65
- function isSafeDashboardUrl(raw) {
66
- try {
67
- const scheme = new URL(raw).protocol;
68
- return 'https:' === scheme || 'http:' === scheme;
69
- }
70
- catch {
71
- return false;
72
- }
73
- }
74
60
  function defects(scope, dashboardUrl) {
75
61
  const list = (scope.defects ?? []).slice(0, IMPACT_DEFECT_LIMIT);
76
62
  if (0 === list.length)
@@ -93,9 +79,10 @@ function defects(scope, dashboardUrl) {
93
79
  const more = !linked
94
80
  ? ''
95
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>`;
96
- 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>`;
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>`;
97
84
  }
98
- export function reportBodyHtml(scope, dashboardUrl, account) {
85
+ export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
99
86
  const c = scope.counts;
100
87
  if (0 === c.calls)
101
88
  return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
@@ -118,7 +105,8 @@ export function reportBodyHtml(scope, dashboardUrl, account) {
118
105
  card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
119
106
  card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
120
107
  ].join('');
121
- return `${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
108
+ const identity = `<div class="reticle-report-identity">${accountControlHtml(account, { dashboardUrl, projectName, verdicts: c.verdicts, defects: c.failed })}${syncButtonHtml(dashboardUrl)}</div>`;
109
+ return `${identity}${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
122
110
  }
123
111
  function localOnly(scope, dashboardUrl, account) {
124
112
  if (dashboardUrl !== undefined)
@@ -132,6 +120,8 @@ function localOnly(scope, dashboardUrl, account) {
132
120
  : [REPORT_TEXT.LOCAL_ONLY, REPORT_TEXT.LOCAL_ONLY_ACTION, REPORT_TEXT.LOCAL_ONLY_TAIL];
133
121
  return `<p class="reticle-report-local-only">${lead} <code>${action}</code> ${tail}</p>`;
134
122
  }
123
+ const SYNC_SPIN_MS = 1_200;
124
+ const SYNCING_ATTR = 'data-syncing';
135
125
  export class PresenterReport {
136
126
  #panel;
137
127
  #body;
@@ -175,6 +165,24 @@ export class PresenterReport {
175
165
  e.stopPropagation();
176
166
  void this.#copy(buildReferralText(), e.currentTarget);
177
167
  });
168
+ root.addEventListener('click', (e) => {
169
+ const target = e.target;
170
+ if (!(target instanceof Element))
171
+ return;
172
+ const signin = target.closest(`[${ACCOUNT_SIGNIN_ATTR}]`);
173
+ if (signin !== null) {
174
+ e.stopPropagation();
175
+ void this.#copy(ACCOUNT_TEXT.SIGNIN_COMMAND, signin);
176
+ return;
177
+ }
178
+ const sync = target.closest(`[${SYNC_BTN_ATTR}]`);
179
+ if (sync !== null) {
180
+ e.stopPropagation();
181
+ this.#host.onSyncNow?.();
182
+ sync.setAttribute(SYNCING_ATTR, '1');
183
+ setTimeout(() => sync.removeAttribute(SYNCING_ATTR), SYNC_SPIN_MS);
184
+ }
185
+ });
178
186
  }
179
187
  setSnapshot(snapshot) {
180
188
  this.#snapshot = snapshot;
@@ -229,7 +237,7 @@ export class PresenterReport {
229
237
  this.#body.innerHTML =
230
238
  scope === undefined
231
239
  ? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
232
- : reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account);
240
+ : reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName);
233
241
  }
234
242
  #openShare(url) {
235
243
  window.open(url, '_blank', 'noopener,noreferrer');
@@ -1,5 +1,5 @@
1
1
  import { SessionState } from '@reticlehq/core';
2
- import { type LogKind, type LogResult } from './presenter-log.js';
2
+ import { type LogKind, type LogResult } from './chrome/presenter-log.js';
3
3
  import { type Capabilities } from '../registry/capabilities.js';
4
4
  export interface PresenterRunState {
5
5
  session: string;
@@ -1,5 +1,5 @@
1
1
  import { SessionState } from '@reticlehq/core';
2
- import { LOG_KIND, LOG_RESULT } from './presenter-log.js';
2
+ import { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
3
3
  import { getCapabilities } from '../registry/capabilities.js';
4
4
  export function buildRunState(input) {
5
5
  const start = input.startMs ?? input.now;
@@ -1,5 +1,5 @@
1
1
  import { SETTINGS_PANEL_ATTR, SETTINGS_ATTR, DOCK_ATTR, DOCK_ALIGN_ATTR, SETTINGS_PLACEMENT_ATTR, } from './presenter-config.js';
2
- import { HUD_DROP_SHADOW, HUD_SURFACE_CLASS, HUD_SURFACE_PAINT } from './presenter-hud-chrome.js';
2
+ import { HUD_DROP_SHADOW, HUD_SURFACE_CLASS, HUD_SURFACE_PAINT, } from './chrome/presenter-hud-chrome.js';
3
3
  export const SETTINGS_CSS = `
4
4
  [${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${'data-reticle-chat-panel'}]{
5
5
  display:none !important;visibility:hidden !important;pointer-events:none !important;}
@@ -149,4 +149,5 @@ export const SETTINGS_CSS = `
149
149
  display:none;pointer-events:none;}
150
150
  [${'data-reticle-overlay'}][data-reticle-hidden="1"] [${DOCK_ATTR}],
151
151
  [${'data-reticle-overlay'}][data-reticle-hidden="1"] [${SETTINGS_PANEL_ATTR}]{display:none !important;}
152
+
152
153
  `;
@@ -1,4 +1,7 @@
1
+ import type { HarnessConfig } from '@reticlehq/core';
1
2
  import { SETTINGS_CSS } from './presenter-settings-styles.js';
3
+ import type { AccountState } from '@reticlehq/core';
4
+ import { type AccountDetails } from './presenter-account.js';
2
5
  export { SETTINGS_CSS };
3
6
  export declare const OutputDetail: {
4
7
  readonly MINIMAL: "minimal";
@@ -38,9 +41,13 @@ export interface PresenterSettings {
38
41
  }
39
42
  export declare function getPresenterSettings(): PresenterSettings;
40
43
  export declare function loadPresenterSettings(): PresenterSettings;
44
+ export declare const SETTINGS_ACCOUNT_ATTR = "data-reticle-settings-account";
45
+ export declare function paintHarnessRow(root: ParentNode, config: HarnessConfig | undefined): void;
46
+ export declare function paintSettingsAccount(root: HTMLElement, account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
41
47
  export declare function settingsPanelHtml(): string;
42
48
  export interface SettingsHost {
43
49
  onHideUntilRestart?: () => void;
50
+ onHarness?: (enabled: boolean) => void;
44
51
  onSettingsChange?: (settings: PresenterSettings) => void;
45
52
  onBeforeOpen?: () => void;
46
53
  }
@@ -52,6 +59,7 @@ export declare class PresenterSettingsPanel {
52
59
  constructor(host?: SettingsHost);
53
60
  contains(node: Node): boolean;
54
61
  mount(root: HTMLElement): void;
62
+ paintHarness(config: HarnessConfig | undefined): void;
55
63
  teardown(): void;
56
64
  isOpen(): boolean;
57
65
  open(): void;
@@ -1,9 +1,10 @@
1
1
  import { SETTINGS_ATTR, SETTINGS_BTN_ATTR, SETTINGS_PANEL_ATTR, SETTINGS_CLOSE_ATTR, SETTING_KEY_ATTR, SETTINGS_STORAGE_KEY, AMBIENT_GLOW_ATTR, BLOCK_ATTR, HIDDEN_UNTIL_RESTART_ATTR, LOG_TIMESTAMPS_ATTR, REDUCE_MOTION_ATTR, DOCK_ATTR, MCP_DOCS_URL, } from './presenter-config.js';
2
- import { PresenterIcon, PRESENTER_ICON_SIZE, setHiIcon, hiIconHtml } from './presenter-icons.js';
3
- import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
2
+ import { PresenterIcon, PRESENTER_ICON_SIZE, setHiIcon, hiIconHtml, } from './icons/presenter-icons.js';
3
+ 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 { accountControlHtml } from './presenter-account.js';
7
8
  export { SETTINGS_CSS };
8
9
  export const OutputDetail = {
9
10
  MINIMAL: 'minimal',
@@ -146,12 +147,47 @@ function settingsCheckRow(key, label, checked) {
146
147
  <span class="reticle-settings-check-label">${label}</span>
147
148
  </label>`;
148
149
  }
150
+ export const SETTINGS_ACCOUNT_ATTR = 'data-reticle-settings-account';
151
+ const SETTINGS_ACCOUNT_ROW_ATTR = 'data-reticle-settings-account-row';
152
+ const SETTINGS_HARNESS_ROW_ATTR = 'data-reticle-settings-harness-row';
153
+ 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.';
154
+ 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.';
155
+ const ACCOUNT_HELP = 'Whether this machine is signed in to a Reticle workspace. Signing in happens in your terminal.';
156
+ export function paintHarnessRow(root, config) {
157
+ const row = root.querySelector(`[${SETTINGS_HARNESS_ROW_ATTR}]`);
158
+ if (!(row instanceof HTMLElement))
159
+ return;
160
+ if (config === undefined) {
161
+ row.hidden = true;
162
+ return;
163
+ }
164
+ row.hidden = false;
165
+ const toggle = row.querySelector(`[${SETTING_KEY_ATTR}="harnessEnabled"]`);
166
+ if (!(toggle instanceof HTMLElement))
167
+ return;
168
+ const usable = config.harnessEntitled;
169
+ toggle.setAttribute('aria-checked', config.harnessEnabled && usable ? 'true' : 'false');
170
+ toggle.setAttribute('aria-disabled', usable ? 'false' : 'true');
171
+ const help = row.querySelector('[data-reticle-help]');
172
+ if (help instanceof HTMLElement)
173
+ help.title = usable ? HARNESS_HELP : HARNESS_LOCKED_HELP;
174
+ }
175
+ export function paintSettingsAccount(root, account, dashboardUrl, details = {}) {
176
+ const row = root.querySelector(`[${SETTINGS_ACCOUNT_ROW_ATTR}]`);
177
+ const slot = root.querySelector(`[${SETTINGS_ACCOUNT_ATTR}]`);
178
+ if (!(row instanceof HTMLElement) || !(slot instanceof HTMLElement))
179
+ return;
180
+ const html = accountControlHtml(account, { ...details, dashboardUrl }, true);
181
+ row.hidden = 0 === html.length;
182
+ slot.innerHTML = html;
183
+ }
149
184
  export function settingsPanelHtml() {
150
185
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
151
186
  const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
152
187
  const outputHelp = 'How much detail is included when you copy or export the run';
153
188
  const reactHelp = 'Include React component paths in exported run state when available';
154
189
  const hideHelp = 'Hide the Reticle HUD until you reload the page';
190
+ const harnessHelp = HARNESS_HELP;
155
191
  const tallyHelp = 'Show the pass/fail score pill in the toolbar';
156
192
  const timestampsHelp = 'Show relative timestamps on each activity-log row';
157
193
  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.';
@@ -178,7 +214,13 @@ export function settingsPanelHtml() {
178
214
  ${settingsCheckRow('blockPageInteractions', 'Block page interactions', true)}
179
215
  ${settingsCheckRow('clearOnCopy', 'Clear on copy/send', false)}
180
216
  ${settingsToggleRow('hideUntilRestart', 'Hide Until Restart', hideHelp)}
217
+ ${settingsToggleRow('harnessEnabled', 'Autonomous driving', harnessHelp, `${SETTINGS_HARNESS_ROW_ATTR} hidden`)}
181
218
  ${settingsToggleRow('reduceMotion', 'Reduce motion', motionHelp)}
219
+ <div class="reticle-settings-section">Account</div>
220
+ <div class="reticle-settings-row" ${SETTINGS_ACCOUNT_ROW_ATTR} hidden>
221
+ ${settingsLabel('Workspace', ACCOUNT_HELP)}
222
+ <span ${SETTINGS_ACCOUNT_ATTR}></span>
223
+ </div>
182
224
  <div class="reticle-settings-section">Status theme</div>
183
225
  ${settingsToggleRow('ambientGlow', 'Page glow', glowHelp)}
184
226
  <div class="reticle-settings-themes" data-reticle-settings-themes></div>
@@ -268,7 +310,14 @@ export class PresenterSettingsPanel {
268
310
  if (!(toggle instanceof HTMLElement))
269
311
  return;
270
312
  const key = toggle.getAttribute(SETTING_KEY_ATTR);
271
- if ('reactComponents' === key) {
313
+ if ('harnessEnabled' === key) {
314
+ if ('true' === toggle.getAttribute('aria-disabled'))
315
+ return;
316
+ const next = 'true' !== toggle.getAttribute('aria-checked');
317
+ toggle.setAttribute('aria-checked', next ? 'true' : 'false');
318
+ this.#host.onHarness?.(next);
319
+ }
320
+ else if ('reactComponents' === key) {
272
321
  this.#update({ reactComponents: !activeSettings.reactComponents });
273
322
  }
274
323
  else if ('hideUntilRestart' === key) {
@@ -350,6 +399,12 @@ export class PresenterSettingsPanel {
350
399
  applyPresenterSettings(root, activeSettings);
351
400
  this.#host.onSettingsChange?.(activeSettings);
352
401
  }
402
+ paintHarness(config) {
403
+ const root = this.#panel;
404
+ if (root === undefined)
405
+ return;
406
+ paintHarnessRow(root, config);
407
+ }
353
408
  teardown() {
354
409
  this.#root = undefined;
355
410
  this.#panel = undefined;
@@ -1,7 +1,7 @@
1
- import { LOG_TIME_ATTR } from './presenter-log.js';
1
+ import { LOG_TIME_ATTR } from './chrome/presenter-log.js';
2
2
  import { Z_HUD_TOOLTIP } from './presenter-config.js';
3
3
  import { CHAT_ATTR, CHAT_PILL_ATTR, CHAT_PLACEMENT_ATTR, DOCK_ALIGN_ATTR, DOCK_ATTR, LOG_TIMESTAMPS_ATTR, MIN_ATTR, REDUCE_MOTION_ATTR, LIVENESS_ATTR, } from './presenter-config.js';
4
- import { HUD_DROP_SHADOW, HUD_SURFACE_FILL } from './presenter-hud-chrome.js';
4
+ import { HUD_DROP_SHADOW, HUD_SURFACE_FILL } from './chrome/presenter-hud-chrome.js';
5
5
  const OVERLAY = 'data-reticle-overlay';
6
6
  const HUD = 'data-reticle-hud';
7
7
  const CHAT_PANEL = 'data-reticle-chat-panel';
@@ -83,11 +83,23 @@ 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
89
- * clicked. Minimise used to leave the toolbar alone above an empty gap, so a minimised session
90
- * looked identical to no session at all.
101
+ * clicked. Leaving the toolbar alone above an empty gap would make a minimised session look
102
+ * identical to no session at all.
91
103
  */
92
104
  [${CHAT_PILL_ATTR}]{
93
105
  display:none;position:absolute;right:0;left:auto;bottom:calc(100% + 8px);z-index:5;
@@ -115,6 +127,13 @@ export const SHELL_CSS = `
115
127
  flex:none;display:flex;align-items:center;gap:8px;padding:11px 44px 9px 14px;
116
128
  border-bottom:1px solid rgba(255,255,255,.06);}
117
129
  [${CHAT_PANEL}] .reticle-chat-brand{display:inline-flex;align-items:center;gap:7px;color:var(--reticle-fg);}
130
+ /* The account capsule lives in the header, not the toolbar. The toolbar row is already 13px over its
131
+ own width, so a 20px avatar there pushed the close button outside the panel. The header has the
132
+ space, is visible under exactly the same condition, and is where an account avatar belongs anyway.
133
+ margin-left:auto right-aligns it; the head reserves 44px of right padding for the caret above it,
134
+ so the two never overlap. The :empty rule keeps the gap closed when no account state was sent. */
135
+ [${CHAT_PANEL}] .reticle-head-account{margin-left:auto;display:inline-flex;align-items:center;}
136
+ [${CHAT_PANEL}] .reticle-head-account:empty{display:none;}
118
137
  [${CHAT_PANEL}] .reticle-chat-brand .reticle-mark{height:14px;width:auto;}
119
138
  [${CHAT_PANEL}] .reticle-chat-brandname{font-size:12.5px;font-weight:600;letter-spacing:.01em;}
120
139
  /* No state word here: the activity strip immediately below already says "idle · 21s", and the
@@ -234,6 +253,7 @@ export const SHELL_CSS = `
234
253
  padding:2px;border-radius:999px;background:rgba(0,0,0,.2);}
235
254
  [${HUD}] .reticle-toolbar-chrome{display:inline-flex;align-items:center;gap:2px;flex:none;}
236
255
  [${HUD}] .reticle-tb-sep{width:1px;height:14px;background:rgba(255,255,255,.12);margin:0 4px;flex:none;align-self:center;}
256
+
237
257
  [${HUD}] .reticle-tb-wrap{position:relative;display:flex;align-items:center;justify-content:center;overflow:visible;}
238
258
  [${HUD}] .reticle-tb-btn{
239
259
  flex:none;display:inline-flex;align-items:center;justify-content:center;
@@ -259,10 +279,9 @@ export const SHELL_CSS = `
259
279
  [${HUD}] .reticle-hi-toggle .reticle-hi-icon--solid svg{
260
280
  transform:scale(1.12);transform-origin:center;}
261
281
  /*
262
- * Active toggles keep the OUTLINE icon. They used to swap to the solid heroicon, which is a filled
263
- * glyph next to 1.5px strokes everywhere else, so the pressed button read as a heavier typeface
264
- * rather than as a state. The state is already carried by accent colour and a background above,
265
- * which is enough and does not change the icon's weight.
282
+ * Active toggles keep the OUTLINE icon. The solid heroicon is a filled glyph next to 1.5px strokes
283
+ * everywhere else, so a pressed button reads as a heavier typeface rather than as a state. Accent
284
+ * colour and the background above already carry the state without changing the icon's weight.
266
285
  */
267
286
  [${HUD}] .reticle-tb-btn--toggle[data-active="1"] .reticle-hi-icon--outline{opacity:1;}
268
287
  [${HUD}] .reticle-tb-btn--toggle .reticle-hi-icon--solid{opacity:0;}
@@ -274,10 +293,10 @@ export const SHELL_CSS = `
274
293
  /**
275
294
  * The toolbar is a FIXED number of slots.
276
295
  *
277
- * Copy and Export appear when a session ends, and they used to be added to a bar that was already
278
- * full - eleven icons in a pill sized for nine, so the last one rendered outside the rounded box.
279
- * They take the two slots that Pause and End vacate: neither can do anything to a session that has
280
- * already ended, so the bar swaps two dead controls for two live ones and never changes width.
296
+ * Copy and Export appear when a session ends, and the bar is already full - eleven icons in a pill
297
+ * sized for nine renders the last one outside the rounded box. They take the two slots Pause and End
298
+ * vacate: neither can do anything to an ended session, so the bar swaps two dead controls for two
299
+ * live ones and never changes width.
281
300
  */
282
301
  [${HUD}] .reticle-tb-btn--export{display:none;}
283
302
  [${OVERLAY}][${STATE}="ended"] [${HUD}] .reticle-tb-btn--export{display:inline-flex;}
@@ -307,9 +326,8 @@ export const SHELL_CSS = `
307
326
  * The mode, ON the status row rather than under it.
308
327
  *
309
328
  * The action text beside it is flex:1, so this claims the right edge of the row and takes its space
310
- * from the elided action text. Nothing above or below moves: the panel used to grow and shrink by
311
- * the height of this pill on every single tool call, which is what made one status line look like a
312
- * second UI appearing and leaving.
329
+ * from the elided action text. Nothing above or below moves: growing and shrinking by the height of
330
+ * this pill on every tool call makes one status line look like a second UI appearing and leaving.
313
331
  */
314
332
  [${DOCK_ATTR}] .reticle-chip{display:none;flex:none;align-items:center;gap:4px;font-size:8px;font-weight:600;letter-spacing:.06em;
315
333
  height:16px;padding:0 7px;line-height:1;border-radius:999px;text-transform:uppercase;
@@ -1,4 +1,8 @@
1
+ import type { HarnessConfig } from '@reticlehq/core';
1
2
  import { PresenterReport } from './presenter-report.js';
3
+ import type { AccountState } from '@reticlehq/core';
4
+ import { type AccountDetails } from './presenter-account.js';
5
+ import { type OfferState } from './presenter-offer.js';
2
6
  import { type SettingsHost } from './presenter-settings.js';
3
7
  interface HudShellCallbacks {
4
8
  onChatOpen?: () => void;
@@ -6,10 +10,14 @@ interface HudShellCallbacks {
6
10
  onExpand?: () => void;
7
11
  onAnnotateToggle?: (on: boolean) => void;
8
12
  onCollapse?: () => void;
13
+ onSyncNow?: () => void;
9
14
  settings?: SettingsHost;
10
15
  }
11
16
  export declare class HudShell {
12
17
  #private;
18
+ paintHarness(config: HarnessConfig | undefined): void;
19
+ paintAccount(account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
20
+ paintOffer(offer: OfferState | undefined): void;
13
21
  constructor(callbacks?: HudShellCallbacks);
14
22
  static dockHtml(actStripHtml: string, bannerHtml: string, logAttr: string, flowsHtml: string, footHtml: string): string;
15
23
  get report(): PresenterReport;
@@ -1,11 +1,15 @@
1
1
  import { PresenterReport, reportPanelHtml } from './presenter-report.js';
2
+ import { paintToolbarAccount, TOOLBAR_ACCOUNT_ATTR } from './presenter-workspace.js';
3
+ import { mountAccountControl } from './presenter-account.js';
4
+ import { paintSettingsAccount } from './presenter-settings.js';
2
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';
3
- import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './presenter-brand.js';
4
- import { settleLogAtLatest } from './presenter-log.js';
6
+ import { OFFER_SLOT_ATTR, paintOffer } from './presenter-offer.js';
7
+ import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './chrome/presenter-brand.js';
8
+ import { settleLogAtLatest } from './chrome/presenter-log.js';
5
9
  import { installHudDragHandles, installHudPositionGuards } from './presenter-drag.js';
6
10
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
7
- import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './presenter-icons.js';
8
- import { HUD_SURFACE_CLASS, HUD_LOG_WELL_CLASS } from './presenter-hud-chrome.js';
11
+ import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
12
+ import { HUD_SURFACE_CLASS, HUD_LOG_WELL_CLASS } from './chrome/presenter-hud-chrome.js';
9
13
  import { CONTROLS_TOOLBAR_HTML } from './presenter-controls.js';
10
14
  import { PresenterSettingsPanel, settingsPanelHtml, } from './presenter-settings.js';
11
15
  const DRAG_HANDLE_CLASS = 'reticle-toolbar-drag';
@@ -37,8 +41,36 @@ export class HudShell {
37
41
  this.#settings.close();
38
42
  this.closeChat();
39
43
  },
44
+ onSyncNow: () => this.#callbacks.onSyncNow?.(),
40
45
  });
41
46
  #callbacks;
47
+ #pushedAccount;
48
+ #accountTeardown;
49
+ paintHarness(config) {
50
+ this.#settings.paintHarness(config);
51
+ }
52
+ paintAccount(account, dashboardUrl, details = {}) {
53
+ this.#pushedAccount = { account, dashboardUrl, details };
54
+ if (this.#root === undefined)
55
+ return;
56
+ paintToolbarAccount(this.#root, account, dashboardUrl, details);
57
+ paintSettingsAccount(this.#root, account, dashboardUrl, details);
58
+ }
59
+ #pushedOffer;
60
+ paintOffer(offer) {
61
+ this.#pushedOffer = offer;
62
+ if (this.#root === undefined)
63
+ return;
64
+ paintOffer(this.#root, offer, this.#storage());
65
+ }
66
+ #storage() {
67
+ try {
68
+ return globalThis.localStorage;
69
+ }
70
+ catch {
71
+ return undefined;
72
+ }
73
+ }
42
74
  constructor(callbacks = {}) {
43
75
  this.#callbacks = callbacks;
44
76
  this.#settings = new PresenterSettingsPanel({
@@ -59,11 +91,13 @@ export class HudShell {
59
91
  <div ${CHAT_PANEL_ATTR} class="reticle-chat-panel ${HUD_SURFACE_CLASS}" role="region" aria-label="Reticle session" aria-hidden="true">
60
92
  <div class="reticle-chat-head">
61
93
  <span class="reticle-chat-brand">${MARK_SVG}<span class="reticle-chat-brandname">${BRAND_NAME}</span></span>
94
+ <span ${TOOLBAR_ACCOUNT_ATTR} class="reticle-head-account"></span>
62
95
  </div>
63
96
  <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>
64
97
  ${actStripHtml}
65
98
  <span class="reticle-tally" data-reticle-tally hidden></span>
66
99
  ${bannerHtml}
100
+ <div ${OFFER_SLOT_ATTR}></div>
67
101
  <div class="${HUD_LOG_WELL_CLASS}"><div ${logAttr}></div></div>
68
102
  ${flowsHtml}
69
103
  ${footHtml}
@@ -209,8 +243,17 @@ export class HudShell {
209
243
  }
210
244
  document.addEventListener('pointerdown', this.#onDocPointerDown, { signal });
211
245
  document.addEventListener('keydown', this.#onKeyDown, { signal });
246
+ this.#accountTeardown = mountAccountControl(root);
247
+ if (this.#pushedAccount !== undefined) {
248
+ const pushed = this.#pushedAccount;
249
+ this.paintAccount(pushed.account, pushed.dashboardUrl, pushed.details);
250
+ }
251
+ if (this.#pushedOffer !== undefined)
252
+ this.paintOffer(this.#pushedOffer);
212
253
  }
213
254
  teardown() {
255
+ this.#accountTeardown?.();
256
+ this.#accountTeardown = undefined;
214
257
  this.#listeners?.abort();
215
258
  this.#listeners = undefined;
216
259
  this.#toggleSync?.disconnect();
@@ -1,2 +1,3 @@
1
- export declare const Z_OVERLAY = 2147483600;
1
+ import { Z_OVERLAY } from './chrome/layers.js';
2
+ export { Z_OVERLAY };
2
3
  export declare const PRESENTER_CSS: string;
@@ -1,10 +1,13 @@
1
- import { LOG_CSS } from './presenter-log.js';
1
+ import { Z_OVERLAY } from './chrome/layers.js';
2
+ import { LOG_CSS } from './chrome/presenter-log.js';
2
3
  import { CONTROLS_CSS } from './presenter-controls.js';
3
4
  import { SHELL_CSS } from './presenter-shell-styles.js';
4
5
  import { SETTINGS_CSS } from './presenter-settings-styles.js';
5
- import { HUD_CHROME_CSS, HUD_LOG_WELL_CSS } from './presenter-hud-chrome.js';
6
+ import { ACCOUNT_CSS } from './presenter-account-styles.js';
7
+ import { HUD_CHROME_CSS, HUD_LOG_WELL_CSS } from './chrome/presenter-hud-chrome.js';
6
8
  import { REPORT_CSS } from './presenter-report-styles.js';
7
- export const Z_OVERLAY = 2147483600;
9
+ import { OFFER_CSS } from './presenter-offer.js';
10
+ export { Z_OVERLAY };
8
11
  export const PRESENTER_CSS = `
9
12
  [data-reticle-overlay]{position:fixed;inset:0;pointer-events:none;z-index:${String(Z_OVERLAY)};}
10
13
  /**
@@ -60,5 +63,7 @@ ${HUD_LOG_WELL_CSS}
60
63
  ${SHELL_CSS}
61
64
  ${SETTINGS_CSS}
62
65
  ${REPORT_CSS}
66
+ ${ACCOUNT_CSS}
67
+ ${OFFER_CSS}
63
68
  ${LOG_CSS}
64
69
  ${CONTROLS_CSS}`;