@reticlehq/browser 2.13.0 → 2.14.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 (196) hide show
  1. package/dist/actions/actions-dom.d.ts +0 -20
  2. package/dist/actions/actions-dom.js +0 -66
  3. package/dist/actions/actions-press.d.ts +10 -0
  4. package/dist/actions/actions-press.js +77 -0
  5. package/dist/actions/actions.d.ts +0 -89
  6. package/dist/actions/actions.js +24 -358
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +0 -36
  9. package/dist/actions/danger-context.d.ts +3 -0
  10. package/dist/actions/danger-context.js +39 -0
  11. package/dist/actions/scroll.d.ts +0 -9
  12. package/dist/actions/scroll.js +0 -7
  13. package/dist/actions/synthetic-input.d.ts +0 -7
  14. package/dist/actions/synthetic-input.js +0 -27
  15. package/dist/actions/value-input.d.ts +0 -30
  16. package/dist/actions/value-input.js +0 -33
  17. package/dist/commands/commands.d.ts +0 -2
  18. package/dist/commands/commands.js +0 -67
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +1 -88
  22. package/dist/dom/auto-anchor.d.ts +0 -40
  23. package/dist/dom/auto-anchor.js +0 -33
  24. package/dist/dom/chart.d.ts +0 -61
  25. package/dist/dom/chart.js +0 -60
  26. package/dist/dom/desktop-capture.d.ts +0 -30
  27. package/dist/dom/desktop-capture.js +0 -72
  28. package/dist/dom/dom-ignore.d.ts +0 -30
  29. package/dist/dom/dom-ignore.js +0 -47
  30. package/dist/dom/occlusion.d.ts +0 -7
  31. package/dist/dom/occlusion.js +0 -7
  32. package/dist/dom/query.d.ts +0 -14
  33. package/dist/dom/query.js +48 -214
  34. package/dist/dom/realm.d.ts +0 -20
  35. package/dist/dom/realm.js +0 -28
  36. package/dist/dom/refs.d.ts +0 -76
  37. package/dist/dom/refs.js +2 -133
  38. package/dist/dom/shadow-registry.d.ts +0 -33
  39. package/dist/dom/shadow-registry.js +2 -72
  40. package/dist/dom/snapshot.d.ts +2 -43
  41. package/dist/dom/snapshot.js +26 -151
  42. package/dist/dom/source.d.ts +0 -44
  43. package/dist/dom/source.js +0 -45
  44. package/dist/dom/theme.d.ts +0 -37
  45. package/dist/dom/theme.js +2 -52
  46. package/dist/dom/virtualized.d.ts +0 -17
  47. package/dist/dom/virtualized.js +0 -27
  48. package/dist/edit-epoch.d.ts +0 -22
  49. package/dist/edit-epoch.js +0 -52
  50. package/dist/index.js +1 -23
  51. package/dist/observers/animation.d.ts +0 -1
  52. package/dist/observers/animation.js +0 -3
  53. package/dist/observers/blind-spots.d.ts +0 -26
  54. package/dist/observers/blind-spots.js +2 -61
  55. package/dist/observers/console.d.ts +1 -2
  56. package/dist/observers/console.js +41 -30
  57. package/dist/observers/context-open.d.ts +1 -11
  58. package/dist/observers/context-open.js +10 -14
  59. package/dist/observers/departure.d.ts +3 -0
  60. package/dist/observers/departure.js +52 -0
  61. package/dist/observers/dialogs.d.ts +2 -0
  62. package/dist/observers/dialogs.js +39 -0
  63. package/dist/observers/dom.js +0 -44
  64. package/dist/observers/download.d.ts +1 -2
  65. package/dist/observers/download.js +38 -55
  66. package/dist/observers/focus.d.ts +0 -6
  67. package/dist/observers/focus.js +0 -7
  68. package/dist/observers/frames.d.ts +0 -9
  69. package/dist/observers/frames.js +1 -28
  70. package/dist/observers/health.d.ts +0 -5
  71. package/dist/observers/health.js +1 -33
  72. package/dist/observers/install-all.d.ts +2 -0
  73. package/dist/observers/install-all.js +26 -27
  74. package/dist/observers/ipc.d.ts +0 -25
  75. package/dist/observers/ipc.js +0 -65
  76. package/dist/observers/navigation.d.ts +2 -0
  77. package/dist/observers/navigation.js +33 -0
  78. package/dist/observers/network-body.d.ts +5 -28
  79. package/dist/observers/network-body.js +11 -108
  80. package/dist/observers/network-redact.d.ts +1 -20
  81. package/dist/observers/network-redact.js +1 -80
  82. package/dist/observers/network-stream.d.ts +0 -21
  83. package/dist/observers/network-stream.js +1 -44
  84. package/dist/observers/network.d.ts +2 -26
  85. package/dist/observers/network.js +177 -294
  86. package/dist/observers/perf.d.ts +0 -10
  87. package/dist/observers/perf.js +0 -16
  88. package/dist/observers/route.d.ts +1 -2
  89. package/dist/observers/route.js +13 -19
  90. package/dist/observers/scroll.d.ts +0 -1
  91. package/dist/observers/scroll.js +0 -9
  92. package/dist/observers/sdk-failure.d.ts +1 -9
  93. package/dist/observers/sdk-failure.js +1 -30
  94. package/dist/observers/state.d.ts +1 -11
  95. package/dist/observers/state.js +13 -41
  96. package/dist/observers/storage.d.ts +0 -14
  97. package/dist/observers/storage.js +10 -34
  98. package/dist/observers/types.d.ts +0 -15
  99. package/dist/observers/types.js +0 -14
  100. package/dist/patching/capture-method.d.ts +0 -24
  101. package/dist/patching/capture-method.js +0 -26
  102. package/dist/presenter/overlay.d.ts +0 -1
  103. package/dist/presenter/overlay.js +0 -1
  104. package/dist/presenter/presenter-brand.d.ts +0 -19
  105. package/dist/presenter/presenter-brand.js +0 -21
  106. package/dist/presenter/presenter-config.d.ts +1 -105
  107. package/dist/presenter/presenter-config.js +2 -91
  108. package/dist/presenter/presenter-controls.d.ts +0 -29
  109. package/dist/presenter/presenter-controls.js +3 -131
  110. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  111. package/dist/presenter/presenter-dock-layout.js +0 -4
  112. package/dist/presenter/presenter-drag.d.ts +2 -24
  113. package/dist/presenter/presenter-drag.js +0 -26
  114. package/dist/presenter/presenter-effects.d.ts +0 -9
  115. package/dist/presenter/presenter-effects.js +0 -9
  116. package/dist/presenter/presenter-glow.d.ts +0 -21
  117. package/dist/presenter/presenter-glow.js +1 -27
  118. package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
  119. package/dist/presenter/presenter-heroicons-data.js +0 -8
  120. package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
  121. package/dist/presenter/presenter-hud-chrome.js +0 -10
  122. package/dist/presenter/presenter-icons.d.ts +0 -6
  123. package/dist/presenter/presenter-icons.js +0 -6
  124. package/dist/presenter/presenter-log.d.ts +0 -38
  125. package/dist/presenter/presenter-log.js +1 -45
  126. package/dist/presenter/presenter-report-copy.d.ts +5 -66
  127. package/dist/presenter/presenter-report-copy.js +5 -66
  128. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  129. package/dist/presenter/presenter-report-styles.js +6 -6
  130. package/dist/presenter/presenter-report.d.ts +4 -7
  131. package/dist/presenter/presenter-report.js +18 -76
  132. package/dist/presenter/presenter-run-state.d.ts +0 -7
  133. package/dist/presenter/presenter-run-state.js +0 -5
  134. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  135. package/dist/presenter/presenter-settings-styles.js +0 -1
  136. package/dist/presenter/presenter-settings.d.ts +1 -23
  137. package/dist/presenter/presenter-settings.js +1 -31
  138. package/dist/presenter/presenter-shell-styles.js +2 -5
  139. package/dist/presenter/presenter-shell.d.ts +2 -18
  140. package/dist/presenter/presenter-shell.js +1 -73
  141. package/dist/presenter/presenter-styles.d.ts +0 -9
  142. package/dist/presenter/presenter-styles.js +0 -9
  143. package/dist/presenter/presenter-tally.d.ts +0 -5
  144. package/dist/presenter/presenter-tally.js +0 -4
  145. package/dist/presenter/presenter-test-helpers.d.ts +0 -2
  146. package/dist/presenter/presenter-test-helpers.js +0 -2
  147. package/dist/presenter/presenter-verbs.js +0 -7
  148. package/dist/presenter/presenter-workspace.d.ts +0 -4
  149. package/dist/presenter/presenter-workspace.js +1 -8
  150. package/dist/presenter/presenter.d.ts +0 -60
  151. package/dist/presenter/presenter.js +4 -118
  152. package/dist/recorder/recorder-styles.d.ts +0 -6
  153. package/dist/recorder/recorder-styles.js +0 -6
  154. package/dist/recorder/recorder.d.ts +0 -34
  155. package/dist/recorder/recorder.js +0 -49
  156. package/dist/registry/adapters.d.ts +0 -10
  157. package/dist/registry/adapters.js +0 -10
  158. package/dist/registry/auto-testids.d.ts +0 -19
  159. package/dist/registry/auto-testids.js +1 -21
  160. package/dist/registry/capabilities.d.ts +0 -31
  161. package/dist/registry/capabilities.js +1 -46
  162. package/dist/registry/commit-and-signal.d.ts +0 -6
  163. package/dist/registry/commit-and-signal.js +1 -7
  164. package/dist/registry/domains.d.ts +0 -8
  165. package/dist/registry/domains.js +0 -7
  166. package/dist/registry/emitter.d.ts +0 -14
  167. package/dist/registry/emitter.js +0 -10
  168. package/dist/registry/store-adapters.d.ts +0 -186
  169. package/dist/registry/store-adapters.js +0 -163
  170. package/dist/registry/stores.d.ts +0 -33
  171. package/dist/registry/stores.js +0 -59
  172. package/dist/reticle-presenter-helpers.d.ts +0 -13
  173. package/dist/reticle-presenter-helpers.js +0 -14
  174. package/dist/reticle.d.ts +1 -52
  175. package/dist/reticle.js +20 -145
  176. package/dist/review/annotator-styles.js +0 -7
  177. package/dist/review/annotator.d.ts +2 -13
  178. package/dist/review/annotator.js +0 -40
  179. package/dist/review/mark-anchor.d.ts +0 -11
  180. package/dist/review/mark-anchor.js +0 -8
  181. package/dist/security/serialization.d.ts +0 -20
  182. package/dist/security/serialization.js +1 -66
  183. package/dist/session-continuity.d.ts +0 -16
  184. package/dist/session-continuity.js +1 -20
  185. package/dist/timers/clock.d.ts +1 -14
  186. package/dist/timers/clock.js +59 -60
  187. package/dist/timers/native-console.js +0 -4
  188. package/dist/timers/native-timers.d.ts +0 -25
  189. package/dist/timers/native-timers.js +3 -33
  190. package/dist/transport/transport.d.ts +0 -51
  191. package/dist/transport/transport.js +3 -129
  192. package/dist/transport/unreachable-message.d.ts +0 -17
  193. package/dist/transport/unreachable-message.js +0 -17
  194. package/dist/util/captured-method.d.ts +0 -35
  195. package/dist/util/captured-method.js +0 -35
  196. package/package.json +2 -2
@@ -1,49 +1,15 @@
1
1
  import { ImpactSnapshotSchema } from '@reticlehq/core';
2
- /**
3
- * What the report SAYS, kept away from how it looks.
4
- *
5
- * The wording here is the product's only claim about its own worth, so it is written once, in one
6
- * place, and tested. Two audiences, two registers:
7
- *
8
- * - The REPORT is private. It is the user's own data, so it can carry everything, estimates
9
- * included, as long as each estimate shows what it is measured against.
10
- * - The SHARE card is public, and the research on developer stat-cards is blunt about what happens
11
- * to a public card that leads with vendor savings: estimated token/time savings are the most
12
- * mocked class of number in AI tooling (JetBrains measured ~10% against a viral 65% claim), and
13
- * percentiles without a denominator get called misleading. So the card leads with what the
14
- * user's own setup CAUGHT, and publishes its unknowns - the credibility move, and the thing a
15
- * scoreboard would never do.
16
- */
17
- /** Reticle's own links, as they appear in the report footer. */
18
2
  export const REPORT_LINKS = {
19
3
  DOCS: 'https://docs.reticle.sh',
20
4
  SITE: 'https://reticle.sh',
21
5
  GITHUB: 'https://github.com/reticlehq/reticle',
22
6
  DISCORD: 'https://discord.gg/BwAbzv9ZRz',
23
7
  };
24
- /**
25
- * The X handle used for `via=` on a share.
26
- *
27
- * Empty until somebody who owns the account fills it in, and the share URL omits the parameter
28
- * while it is - a guessed handle credits a stranger, and there is no way for code to know.
29
- */
30
8
  export const SHARE_VIA_HANDLE = '';
31
9
  export const REPORT_TEXT = {
32
10
  TITLE: 'Impact',
33
11
  PROJECT: 'This project',
34
12
  GLOBAL: 'Everything on this machine',
35
- /**
36
- * What `counts.failed` actually is: a verdict whose declared consequence did not hold.
37
- *
38
- * It used to read "defects caught before you saw them", which overclaims in the one direction a
39
- * verification tool must never overclaim. A failed verdict is not proof of a defect in the app —
40
- * it is equally the shape of an assertion that was wrong. Measured in the field: an agent
41
- * asserted a clean console on an app with ordinary dev-mode logging, the verdict went red, and
42
- * the panel reported a defect nobody had found.
43
- *
44
- * The honest word is what Reticle DID: it refused to pass them. That is true of the assertion
45
- * error and the real bug alike, and it still reads as the tool having done its job.
46
- */
47
13
  HERO_DEFECTS: 'checks Reticle refused to pass',
48
14
  VERDICTS: 'Verdicts',
49
15
  PASSED: 'passed',
@@ -61,21 +27,14 @@ export const REPORT_TEXT = {
61
27
  ESTIMATE_TAG: 'estimate',
62
28
  CHART: 'Verdicts, last 30 days',
63
29
  DEFECTS: 'What broke',
64
- DEFECTS_MORE: 'Manage all of them on the dashboard',
65
- /**
66
- * What an UNLINKED user is told, and the only place the product tells them.
67
- *
68
- * The dashboard link renders only when `dashboardUrl` exists, which means only once a repo is
69
- * already linked — so the person most likely to want one, watching this record climb on their own
70
- * machine, was never told it existed. The single mention anywhere else is a daemon log line.
71
- *
72
- * Stated as a FACT about where the record lives, not as a pitch. It sits at the foot of a panel
73
- * somebody opened on purpose, so it informs rather than interrupts, and it appears only once
74
- * there is a verdict worth keeping — an offer to preserve nothing is just an advert.
75
- */
30
+ DEFECTS_MORE: 'Manage all of them on the dashboard — triage, assign, and push to GitHub',
31
+ DEFECT_LINK_TITLE: 'Open on the dashboard to triage or push to GitHub',
76
32
  LOCAL_ONLY: 'This record stops at this machine.',
77
33
  LOCAL_ONLY_ACTION: 'reticle login',
78
34
  LOCAL_ONLY_TAIL: 'keeps it, and lets a team see it.',
35
+ UNLINKED: 'Signed in, but this repo is not linked.',
36
+ UNLINKED_ACTION: 'reticle link',
37
+ UNLINKED_TAIL: 'sends this record to your workspace.',
79
38
  DEFECTS_NONE: 'Nothing has failed a declared consequence yet.',
80
39
  EMPTY: 'Nothing recorded yet. Drive the app once and this fills in.',
81
40
  SHARE: 'Share',
@@ -83,7 +42,6 @@ export const REPORT_TEXT = {
83
42
  COPIED: 'Copied',
84
43
  REFER: 'Send to a friend',
85
44
  };
86
- /** "4.2k", "1.3M" - a number a person reads at a glance rather than counts digits in. */
87
45
  export function compactNumber(n) {
88
46
  if (n < 1000)
89
47
  return String(Math.round(n));
@@ -94,7 +52,6 @@ export function compactNumber(n) {
94
52
  function trim(n) {
95
53
  return n >= 10 ? String(Math.round(n)) : String(Math.round(n * 10) / 10);
96
54
  }
97
- /** "2h 14m", "47m", "38s" - the same duration vocabulary the activity strip uses. */
98
55
  export function compactDuration(ms) {
99
56
  const s = Math.floor(ms / 1000);
100
57
  if (s < 60)
@@ -105,13 +62,6 @@ export function compactDuration(ms) {
105
62
  const h = Math.floor(m / 60);
106
63
  return 0 === m % 60 ? `${String(h)}h` : `${String(h)}h ${String(m % 60)}m`;
107
64
  }
108
- /**
109
- * The post itself.
110
- *
111
- * Leads with defects caught, then the verdict split INCLUDING unknowns, then the superlative. No
112
- * estimated savings, no percentile, no leaderboard: those are the four things the evidence says get
113
- * a dev-tool card mocked rather than shared.
114
- */
115
65
  export function buildShareText(scope, projectName) {
116
66
  const c = scope.counts;
117
67
  const where = projectName !== undefined && projectName.length > 0 ? ` on ${projectName}` : '';
@@ -127,29 +77,18 @@ export function buildShareText(scope, projectName) {
127
77
  }
128
78
  return lines.join('\n');
129
79
  }
130
- /** X share intent. `via` is omitted while no handle is configured. */
131
80
  export function buildXShareUrl(text) {
132
81
  const params = new URLSearchParams({ text, url: REPORT_LINKS.SITE });
133
82
  if (SHARE_VIA_HANDLE.length > 0)
134
83
  params.set('via', SHARE_VIA_HANDLE);
135
84
  return `https://twitter.com/intent/tweet?${params.toString()}`;
136
85
  }
137
- /**
138
- * LinkedIn share.
139
- *
140
- * `share-offsite` takes a url and NOTHING else - the title/summary parameters were deprecated in
141
- * 2018 and are ignored, so the caption has to reach the composer some other way. The report copies
142
- * it to the clipboard before opening this, which is the only path that works without asking the
143
- * user for posting permissions.
144
- */
145
86
  export function buildLinkedInShareUrl() {
146
87
  return `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(REPORT_LINKS.SITE)}`;
147
88
  }
148
- /** What a referral says: the tool, not the sender's numbers. */
149
89
  export function buildReferralText() {
150
90
  return `I have been verifying my agent's work from inside the running app with Reticle - it returns pass/fail with the file:line to fix. ${REPORT_LINKS.SITE}`;
151
91
  }
152
- /** Narrow an unknown push payload to a snapshot. Invalid shapes are ignored, never rendered. */
153
92
  export function parseImpactSnapshot(value) {
154
93
  const parsed = ImpactSnapshotSchema.safeParse(value);
155
94
  return parsed.success ? parsed.data : undefined;
@@ -1,7 +1 @@
1
- /**
2
- * The impact report's surface.
3
- *
4
- * Same glass as the chat panel and anchored in the same slot above the toolbar, because it is the
5
- * same object seen a different way: what this session is doing, and what every session has done.
6
- */
7
1
  export declare const REPORT_CSS: string;
@@ -1,12 +1,6 @@
1
1
  import { HUD_DROP_SHADOW } from './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
- /**
5
- * The impact report's surface.
6
- *
7
- * Same glass as the chat panel and anchored in the same slot above the toolbar, because it is the
8
- * same object seen a different way: what this session is doing, and what every session has done.
9
- */
10
4
  export const REPORT_CSS = `
11
5
  [${REPORT_PANEL_ATTR}]{
12
6
  position:absolute;right:0;left:auto;bottom:calc(100% + 12px);z-index:31;
@@ -88,6 +82,12 @@ export const REPORT_CSS = `
88
82
  [${REPORT_PANEL_ATTR}] .reticle-report-defect-source{
89
83
  color:var(--reticle-faint);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
90
84
  overflow-wrap:anywhere;}
85
+ .reticle-report-defect-link{flex:none;display:inline-flex;align-items:center;justify-content:center;
86
+ width:20px;height:20px;border-radius:6px;color:var(--reticle-faint);pointer-events:auto;
87
+ opacity:0;transition:opacity .12s ease,color .12s ease,background .12s ease;}
88
+ .reticle-report-defect:hover .reticle-report-defect-link,
89
+ .reticle-report-defect-link:focus-visible{opacity:1;}
90
+ .reticle-report-defect-link:hover{color:var(--reticle-fg);background:rgba(255,255,255,.07);}
91
91
  [${REPORT_PANEL_ATTR}] .reticle-report-defects-more{
92
92
  display:inline-block;margin-top:8px;font-size:10px;color:var(--reticle-c-active);
93
93
  text-decoration:none;}
@@ -1,16 +1,13 @@
1
- import { type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
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): string;
4
- export interface ReportHost {
5
- /** Opened from the toolbar and from the chat, so the shell decides what else must close. */
3
+ export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState): string;
4
+ interface ReportHost {
6
5
  onBeforeOpen?: () => void;
7
6
  }
8
- /** The report panel controller: scope toggle, live repaint, share + referral actions. */
9
7
  export declare class PresenterReport {
10
8
  #private;
11
9
  constructor(host?: ReportHost);
12
10
  mount(root: HTMLElement): void;
13
- /** The record arrived from the daemon. Repaint only if the panel is on screen. */
14
11
  setSnapshot(snapshot: ImpactSnapshot): void;
15
12
  snapshot(): ImpactSnapshot | undefined;
16
13
  isOpen(): boolean;
@@ -18,6 +15,6 @@ export declare class PresenterReport {
18
15
  close(): void;
19
16
  toggle(): void;
20
17
  contains(node: Node): boolean;
21
- /** The post text for whichever scope is showing. */
22
18
  shareText(): string;
23
19
  }
20
+ export {};
@@ -1,14 +1,8 @@
1
- import { IMPACT_DEFECT_LIMIT } from '@reticlehq/core';
1
+ import { IMPACT_DEFECT_LIMIT, } from '@reticlehq/core';
2
2
  import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './presenter-icons.js';
3
3
  import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
4
4
  import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
5
5
  import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './presenter-report-copy.js';
6
- /**
7
- * The impact report: what Reticle has actually done for this user.
8
- *
9
- * Reads the local record only - the panel is a view over `.reticle/impact.json`, pushed by the
10
- * daemon, never fetched from us. Two scopes (this project, this machine) behind one toggle.
11
- */
12
6
  const SCOPE_ATTR = 'data-reticle-report-scope';
13
7
  const SHARE_X_ATTR = 'data-reticle-share-x';
14
8
  const SHARE_IN_ATTR = 'data-reticle-share-in';
@@ -17,7 +11,7 @@ const REFER_ATTR = 'data-reticle-refer';
17
11
  const COPIED_FLASH_MS = 1600;
18
12
  export function reportPanelHtml() {
19
13
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
20
- return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="dialog" aria-label="Reticle impact" aria-hidden="true">
14
+ return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="region" aria-label="Reticle impact" aria-hidden="true">
21
15
  <div class="reticle-report-inner">
22
16
  <div class="reticle-report-head">
23
17
  <span class="reticle-report-title">${REPORT_TEXT.TITLE}</span>
@@ -40,19 +34,12 @@ export function reportPanelHtml() {
40
34
  </div>
41
35
  </div>`;
42
36
  }
43
- /** A stat card: the number, what it is, and - for an estimate - what it is measured against. */
44
37
  function card(value, label, basis) {
45
38
  const tag = basis === undefined
46
39
  ? ''
47
40
  : `<span class="reticle-report-basis" title="${basis}">${REPORT_TEXT.ESTIMATE_TAG}</span>`;
48
41
  return `<div class="reticle-report-card"><span class="reticle-report-value">${value}</span><span class="reticle-report-label">${label}${tag}</span></div>`;
49
42
  }
50
- /**
51
- * A 30-day bar chart of verdicts, as inline spans.
52
- *
53
- * Bars rather than a line: a day with no verdicts is a real fact about the week, and a line chart
54
- * interpolates straight through it.
55
- */
56
43
  function chart(scope) {
57
44
  const days = scope.days.slice(-30);
58
45
  if (0 === days.length)
@@ -68,14 +55,6 @@ function chart(scope) {
68
55
  .join('');
69
56
  return `<div class="reticle-report-chart-wrap"><span class="reticle-report-section">${REPORT_TEXT.CHART}</span><div class="reticle-report-chart">${bars}</div></div>`;
70
57
  }
71
- /**
72
- * Escape text that came from the app under test.
73
- *
74
- * A defect title is an element's accessible name or a verdict's failure reason — both of which are
75
- * ultimately the CONTENT of somebody else's page, and this panel builds its DOM from an HTML string.
76
- * Everything else the report renders is a number or a date; this is the first app-derived text to
77
- * reach it, so the escaping arrives with it.
78
- */
79
58
  function esc(value) {
80
59
  return value
81
60
  .replace(/&/g, '&amp;')
@@ -83,46 +62,23 @@ function esc(value) {
83
62
  .replace(/>/g, '&gt;')
84
63
  .replace(/"/g, '&quot;');
85
64
  }
86
- /**
87
- * The short list of what is currently broken.
88
- *
89
- * The hero number already says HOW MANY defects Reticle caught; this says WHICH ONES, which is the
90
- * difference between a statistic and something a person can act on. Deliberately short: a panel in
91
- * the corner of somebody's app is not a triage queue, and the link at the bottom is where the queue
92
- * lives. When the project is not linked to a workspace there is no link and no nagging — the free
93
- * tool is complete on its own.
94
- */
95
- /**
96
- * Whether a dashboard link is safe to render as a clickable href.
97
- *
98
- * The url arrives from `.reticle/cloud.json` — a file in somebody's repository, which means it is
99
- * INPUT. Escaping the quotes stops it breaking out of the attribute but does nothing about the
100
- * SCHEME, so `javascript:...` produced a link that ran code inside the developer's own application,
101
- * from a panel Reticle injected there. Only the two schemes a dashboard can actually live on are
102
- * allowed; anything else renders no link at all, which is the same state as an unlinked project and
103
- * therefore already a supported one.
104
- */
105
65
  function isSafeDashboardUrl(raw) {
106
66
  try {
107
67
  const scheme = new URL(raw).protocol;
108
68
  return 'https:' === scheme || 'http:' === scheme;
109
69
  }
110
70
  catch {
111
- // Not a url at all. A relative path cannot address a dashboard on another origin, so there is
112
- // nothing to render and nothing lost by refusing it.
113
71
  return false;
114
72
  }
115
73
  }
116
74
  function defects(scope, dashboardUrl) {
117
- /*
118
- * Defaulted here as well as in the schema. Zod's default applies when a record is PARSED, and the
119
- * snapshot reaching this panel is pushed straight from the daemon rather than round-tripped
120
- * through the schema — so a record written by an older build arrives with no `defects` field at
121
- * all and used to throw, taking the whole report down with it.
122
- */
123
75
  const list = (scope.defects ?? []).slice(0, IMPACT_DEFECT_LIMIT);
124
76
  if (0 === list.length)
125
77
  return '';
78
+ const linked = dashboardUrl !== undefined && isSafeDashboardUrl(dashboardUrl);
79
+ const rowLink = linked
80
+ ? `<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>`
81
+ : '';
126
82
  const rows = list
127
83
  .map((d) => {
128
84
  const detail = d.detail === undefined
@@ -131,17 +87,15 @@ function defects(scope, dashboardUrl) {
131
87
  const source = d.source === undefined
132
88
  ? ''
133
89
  : `<span class="reticle-report-defect-source">${esc(d.source)}</span>`;
134
- return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}</li>`;
90
+ return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}${rowLink}</li>`;
135
91
  })
136
92
  .join('');
137
- // Only claim there are more when there actually are — `counts.failed` is every defect ever, and
138
- // this list is the recent tail of it.
139
- const more = dashboardUrl === undefined || !isSafeDashboardUrl(dashboardUrl)
93
+ const more = !linked
140
94
  ? ''
141
95
  : `<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>`;
142
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>`;
143
97
  }
144
- export function reportBodyHtml(scope, dashboardUrl) {
98
+ export function reportBodyHtml(scope, dashboardUrl, account) {
145
99
  const c = scope.counts;
146
100
  if (0 === c.calls)
147
101
  return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
@@ -164,27 +118,20 @@ export function reportBodyHtml(scope, dashboardUrl) {
164
118
  card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
165
119
  card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
166
120
  ].join('');
167
- return `${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl)}`;
121
+ return `${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
168
122
  }
169
- /**
170
- * The one line an UNLINKED user is shown about the dashboard.
171
- *
172
- * Absent `dashboardUrl` IS the unlinked signal — it is only ever set from a repo's cloud.json — so
173
- * this needs no new plumbing and cannot be wrong about the state it describes.
174
- *
175
- * Gated on a VERDICT, not on tool calls. Somebody who has driven the app but proved nothing has not
176
- * yet received the thing this offers to preserve, and offering to keep nothing is an advert. Past
177
- * that bar it is a fact about where their record lives, at the foot of a panel they opened on
178
- * purpose — which is why it does not need to be dismissible.
179
- */
180
- function localOnly(scope, dashboardUrl) {
123
+ function localOnly(scope, dashboardUrl, account) {
181
124
  if (dashboardUrl !== undefined)
182
125
  return '';
183
126
  if (scope.counts.verdicts <= 0)
184
127
  return '';
185
- return `<p class="reticle-report-local-only">${REPORT_TEXT.LOCAL_ONLY} <code>${REPORT_TEXT.LOCAL_ONLY_ACTION}</code> ${REPORT_TEXT.LOCAL_ONLY_TAIL}</p>`;
128
+ if (account === undefined)
129
+ return '';
130
+ const [lead, action, tail] = account.signedIn
131
+ ? [REPORT_TEXT.UNLINKED, REPORT_TEXT.UNLINKED_ACTION, REPORT_TEXT.UNLINKED_TAIL]
132
+ : [REPORT_TEXT.LOCAL_ONLY, REPORT_TEXT.LOCAL_ONLY_ACTION, REPORT_TEXT.LOCAL_ONLY_TAIL];
133
+ return `<p class="reticle-report-local-only">${lead} <code>${action}</code> ${tail}</p>`;
186
134
  }
187
- /** The report panel controller: scope toggle, live repaint, share + referral actions. */
188
135
  export class PresenterReport {
189
136
  #panel;
190
137
  #body;
@@ -217,8 +164,6 @@ export class PresenterReport {
217
164
  });
218
165
  root.querySelector(`[${SHARE_IN_ATTR}]`)?.addEventListener('click', (e) => {
219
166
  e.stopPropagation();
220
- // LinkedIn ignores every text parameter, so the caption goes to the clipboard first and the
221
- // composer opens on the link - the only route that needs no posting permission.
222
167
  void this.#copy(this.shareText());
223
168
  this.#openShare(buildLinkedInShareUrl());
224
169
  });
@@ -231,7 +176,6 @@ export class PresenterReport {
231
176
  void this.#copy(buildReferralText(), e.currentTarget);
232
177
  });
233
178
  }
234
- /** The record arrived from the daemon. Repaint only if the panel is on screen. */
235
179
  setSnapshot(snapshot) {
236
180
  this.#snapshot = snapshot;
237
181
  if (this.isOpen())
@@ -264,7 +208,6 @@ export class PresenterReport {
264
208
  contains(node) {
265
209
  return true === this.#panel?.contains(node);
266
210
  }
267
- /** The post text for whichever scope is showing. */
268
211
  shareText() {
269
212
  const snap = this.#snapshot;
270
213
  if (snap === undefined)
@@ -286,7 +229,7 @@ export class PresenterReport {
286
229
  this.#body.innerHTML =
287
230
  scope === undefined
288
231
  ? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
289
- : reportBodyHtml(scope, this.#snapshot?.dashboardUrl);
232
+ : reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account);
290
233
  }
291
234
  #openShare(url) {
292
235
  window.open(url, '_blank', 'noopener,noreferrer');
@@ -296,7 +239,6 @@ export class PresenterReport {
296
239
  await navigator.clipboard?.writeText(text);
297
240
  }
298
241
  catch {
299
- // A blocked clipboard is not worth an error dialog over; the text is on screen either way.
300
242
  }
301
243
  if (button instanceof HTMLElement) {
302
244
  const original = button.textContent ?? '';
@@ -1,7 +1,6 @@
1
1
  import { SessionState } from '@reticlehq/core';
2
2
  import { type LogKind, type LogResult } from './presenter-log.js';
3
3
  import { type Capabilities } from '../registry/capabilities.js';
4
- /** The in-page run-state export (Copy/Export buttons). The full event ring-buffer is server-side. */
5
4
  export interface PresenterRunState {
6
5
  session: string;
7
6
  url: string;
@@ -24,7 +23,6 @@ export interface PresenterRunState {
24
23
  result?: LogResult;
25
24
  }[];
26
25
  }
27
- /** Everything the controller hands the builder - the live fields a run state is computed from. */
28
26
  interface RunStateInput {
29
27
  sessionId: string;
30
28
  state: SessionState;
@@ -38,10 +36,5 @@ interface RunStateInput {
38
36
  result?: LogResult;
39
37
  }[];
40
38
  }
41
- /**
42
- * Compute the exported run-state from the controller's live fields: per-kind + pass/fail counts over
43
- * the activity log, the session duration, and the current capability surface. Extracted from
44
- * presenter.ts so the controller stays under the size cap; the counting is the same as before.
45
- */
46
39
  export declare function buildRunState(input: RunStateInput): PresenterRunState;
47
40
  export {};
@@ -1,11 +1,6 @@
1
1
  import { SessionState } from '@reticlehq/core';
2
2
  import { LOG_KIND, LOG_RESULT } from './presenter-log.js';
3
3
  import { getCapabilities } from '../registry/capabilities.js';
4
- /**
5
- * Compute the exported run-state from the controller's live fields: per-kind + pass/fail counts over
6
- * the activity log, the session duration, and the current capability surface. Extracted from
7
- * presenter.ts so the controller stays under the size cap; the counting is the same as before.
8
- */
9
4
  export function buildRunState(input) {
10
5
  const start = input.startMs ?? input.now;
11
6
  const counts = { reads: 0, acts: 0, narrations: 0, human: 0, passes: 0, fails: 0 };
@@ -1,2 +1 @@
1
- /** Compact settings card - dock-anchored above the HUD pill. */
2
1
  export declare const SETTINGS_CSS: string;
@@ -1,6 +1,5 @@
1
1
  import { SETTINGS_PANEL_ATTR, SETTINGS_ATTR, DOCK_ATTR, DOCK_ALIGN_ATTR, SETTINGS_PLACEMENT_ATTR, } from './presenter-config.js';
2
2
  import { HUD_DROP_SHADOW, HUD_SURFACE_CLASS, HUD_SURFACE_PAINT } from './presenter-hud-chrome.js';
3
- /** Compact settings card - dock-anchored above the HUD pill. */
4
3
  export const SETTINGS_CSS = `
5
4
  [${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${'data-reticle-chat-panel'}]{
6
5
  display:none !important;visibility:hidden !important;pointer-events:none !important;}
@@ -1,20 +1,11 @@
1
1
  import { SETTINGS_CSS } from './presenter-settings-styles.js';
2
2
  export { SETTINGS_CSS };
3
- /** How much detail lands in copied/exported run state. */
4
3
  export declare const OutputDetail: {
5
4
  readonly MINIMAL: "minimal";
6
5
  readonly STANDARD: "standard";
7
6
  readonly VERBOSE: "verbose";
8
7
  };
9
8
  export type OutputDetail = (typeof OutputDetail)[keyof typeof OutputDetail];
10
- /**
11
- * Status themes: ONE choice, three colours.
12
- *
13
- * This was four separate pickers - a chrome accent plus a swatch row per state - which is 28
14
- * decisions to land on a set that works together. A theme is a combination we have already checked
15
- * reads as a progression (working -> waiting -> done) and against a dark app, so the choice is
16
- * "which mood", not "which four hex values".
17
- */
18
9
  export declare const StatusThemeId: {
19
10
  readonly SIGNAL: "signal";
20
11
  readonly TRAFFIC: "traffic";
@@ -23,7 +14,7 @@ export declare const StatusThemeId: {
23
14
  readonly EMBER: "ember";
24
15
  };
25
16
  export type StatusThemeId = (typeof StatusThemeId)[keyof typeof StatusThemeId];
26
- export interface StatusTheme {
17
+ interface StatusTheme {
27
18
  id: StatusThemeId;
28
19
  label: string;
29
20
  active: string;
@@ -34,13 +25,7 @@ export declare const STATUS_THEMES: readonly StatusTheme[];
34
25
  export declare function statusTheme(id: StatusThemeId): StatusTheme;
35
26
  export interface PresenterSettings {
36
27
  outputDetail: OutputDetail;
37
- /**
38
- * The colours the HUD signals session state with - the dot, the panel wash, the page glow, the
39
- * FAB halo and the annotation marks all read from whichever one is current. One choice, because
40
- * the three have to work as a set and against the user's own app.
41
- */
42
28
  statusThemeId: StatusThemeId;
43
- /** The page-edge glow. Off leaves every other signal (dot, panel, FAB) in place. */
44
29
  ambientGlow: boolean;
45
30
  reactComponents: boolean;
46
31
  hideUntilRestart: boolean;
@@ -53,21 +38,14 @@ export interface PresenterSettings {
53
38
  }
54
39
  export declare function getPresenterSettings(): PresenterSettings;
55
40
  export declare function loadPresenterSettings(): PresenterSettings;
56
- /** Settings panel markup - anchored above the gear in the toolbar. */
57
41
  export declare function settingsPanelHtml(): string;
58
42
  export interface SettingsHost {
59
43
  onHideUntilRestart?: () => void;
60
44
  onSettingsChange?: (settings: PresenterSettings) => void;
61
45
  onBeforeOpen?: () => void;
62
46
  }
63
- /** Apply persisted settings onto the overlay + dock. */
64
47
  export declare function applyPresenterSettings(root: HTMLElement, settings: PresenterSettings): void;
65
- /**
66
- * Toggle the full-page blocker. Only active while annotate mode is live AND the user opted in -
67
- * never on a collapsed FAB, so real hover/click on the host app still works.
68
- */
69
48
  export declare function syncPageBlocker(root: HTMLElement, settings: PresenterSettings, annotateLive: boolean): void;
70
- /** Blocker node - sits under Reticle UI, above the host page. */
71
49
  export declare function blockerHtml(): string;
72
50
  export declare class PresenterSettingsPanel {
73
51
  #private;
@@ -5,20 +5,11 @@ 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
7
  export { SETTINGS_CSS };
8
- /** How much detail lands in copied/exported run state. */
9
8
  export const OutputDetail = {
10
9
  MINIMAL: 'minimal',
11
10
  STANDARD: 'standard',
12
11
  VERBOSE: 'verbose',
13
12
  };
14
- /**
15
- * Status themes: ONE choice, three colours.
16
- *
17
- * This was four separate pickers - a chrome accent plus a swatch row per state - which is 28
18
- * decisions to land on a set that works together. A theme is a combination we have already checked
19
- * reads as a progression (working -> waiting -> done) and against a dark app, so the choice is
20
- * "which mood", not "which four hex values".
21
- */
22
13
  export const StatusThemeId = {
23
14
  SIGNAL: 'signal',
24
15
  TRAFFIC: 'traffic',
@@ -70,14 +61,6 @@ const DEFAULT_SETTINGS = {
70
61
  blockPageInteractions: true,
71
62
  showTally: false,
72
63
  showTimestamps: true,
73
- // ON by default: the chat IS the HUD's content, and shipping it off meant the default experience
74
- // was a bare toolbar until somebody found the toggle. `expand()` has always opened the chat for
75
- // the same reason; this is the other half — session start, with no click at all.
76
- //
77
- // Safe to default because `openChat()` expands a collapsed HUD, and session start is the one
78
- // moment where that is what you want. A user who prefers the bare toolbar turns this off and it
79
- // stays off: a stored `false` is honoured over the default, which is pinned in
80
- // presenter-chat-default.test.ts so the product cannot change somebody's answer behind their back.
81
64
  autoOpenChat: true,
82
65
  reduceMotion: false,
83
66
  };
@@ -101,10 +84,6 @@ export function loadPresenterSettings() {
101
84
  ? o['outputDetail']
102
85
  : DEFAULT_SETTINGS.outputDetail,
103
86
  reactComponents: true === o['reactComponents'],
104
- // NOT read back from storage. "Until restart" has to mean until restart: persisted, it
105
- // survived the reload that was supposed to undo it, and the only way back was clearing
106
- // localStorage by hand - the HUD was simply gone, including the settings panel that turned
107
- // it off. It is a this-page-only switch, so it lives only in memory.
108
87
  hideUntilRestart: false,
109
88
  statusThemeId: isStatusThemeId(o['statusThemeId'])
110
89
  ? o['statusThemeId']
@@ -140,7 +119,6 @@ function persistSettings(next) {
140
119
  localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(next));
141
120
  }
142
121
  catch {
143
- /* quota / private mode - settings still apply for this page */
144
122
  }
145
123
  }
146
124
  function patchSettings(patch) {
@@ -168,7 +146,6 @@ function settingsCheckRow(key, label, checked) {
168
146
  <span class="reticle-settings-check-label">${label}</span>
169
147
  </label>`;
170
148
  }
171
- /** Settings panel markup - anchored above the gear in the toolbar. */
172
149
  export function settingsPanelHtml() {
173
150
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
174
151
  const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
@@ -180,7 +157,7 @@ export function settingsPanelHtml() {
180
157
  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.';
181
158
  const motionHelp = 'Reduce HUD animations for accessibility';
182
159
  const glowHelp = 'Glow the page edges in the status colour while a session is live. Off keeps the HUD signals and leaves your app alone.';
183
- return `<div ${SETTINGS_PANEL_ATTR} class="reticle-settings ${HUD_SURFACE_CLASS}" role="dialog" aria-label="Reticle settings" aria-hidden="true">
160
+ return `<div ${SETTINGS_PANEL_ATTR} class="reticle-settings ${HUD_SURFACE_CLASS}" role="region" aria-label="Reticle settings" aria-hidden="true">
184
161
  <div class="reticle-settings-inner">
185
162
  <div class="reticle-settings-head">
186
163
  <span class="reticle-settings-title">Settings</span>
@@ -213,7 +190,6 @@ export function settingsPanelHtml() {
213
190
  </div>
214
191
  </div>`;
215
192
  }
216
- /** Apply persisted settings onto the overlay + dock. */
217
193
  export function applyPresenterSettings(root, settings) {
218
194
  const theme = statusTheme(settings.statusThemeId);
219
195
  root.style.setProperty('--reticle-mark-accent', theme.active);
@@ -235,14 +211,9 @@ export function applyPresenterSettings(root, settings) {
235
211
  tally.setAttribute('hidden', '');
236
212
  }
237
213
  }
238
- /**
239
- * Toggle the full-page blocker. Only active while annotate mode is live AND the user opted in -
240
- * never on a collapsed FAB, so real hover/click on the host app still works.
241
- */
242
214
  export function syncPageBlocker(root, settings, annotateLive) {
243
215
  root.setAttribute(BLOCK_ATTR, settings.blockPageInteractions && annotateLive ? '1' : '0');
244
216
  }
245
- /** Blocker node - sits under Reticle UI, above the host page. */
246
217
  export function blockerHtml() {
247
218
  return '<div data-reticle-blocker aria-hidden="true"></div>';
248
219
  }
@@ -470,7 +441,6 @@ export class PresenterSettingsPanel {
470
441
  el.textContent = on ? '✓' : '';
471
442
  }
472
443
  }
473
- /** One row of themes: each chip shows the whole set - active, idle, ended - in order. */
474
444
  #buildThemes() {
475
445
  const host = this.#panel?.querySelector('[data-reticle-settings-themes]');
476
446
  if (null === host || undefined === host)
@@ -1,8 +1,5 @@
1
- /**
2
- * Floating HUD shell styles - FAB, morphing toolbar, and agent chat panel.
3
- * Split from presenter-styles.ts so the controller stays under the size cap.
4
- */
5
1
  import { LOG_TIME_ATTR } from './presenter-log.js';
2
+ import { Z_HUD_TOOLTIP } from './presenter-config.js';
6
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';
7
4
  import { HUD_DROP_SHADOW, HUD_SURFACE_FILL } from './presenter-hud-chrome.js';
8
5
  const OVERLAY = 'data-reticle-overlay';
@@ -289,7 +286,7 @@ export const SHELL_CSS = `
289
286
  [${HUD}] .reticle-tb-tip{
290
287
  position:absolute;bottom:calc(100% + 14px);left:50%;transform:translateX(-50%) scale(.95);
291
288
  padding:6px 10px;background:#1a1a1a;color:rgba(255,255,255,.9);font-size:12px;font-weight:500;
292
- border-radius:8px;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;z-index:3;
289
+ border-radius:8px;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;z-index:${String(Z_HUD_TOOLTIP)};
293
290
  box-shadow:0 2px 8px rgba(0,0,0,.3);transition:opacity .135s ease,transform .135s ease,visibility .135s;}
294
291
  [${HUD}] .reticle-tb-tip::after{
295
292
  content:"";position:absolute;top:calc(100% - 4px);left:50%;transform:translateX(-50%) rotate(45deg);