@reticlehq/browser 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/actions/action-report.d.ts +54 -0
  2. package/dist/actions/action-report.js +60 -0
  3. package/dist/actions/actions-dom.d.ts +1 -0
  4. package/dist/actions/actions-dom.js +56 -43
  5. package/dist/actions/actions-press.d.ts +11 -4
  6. package/dist/actions/actions-press.js +132 -17
  7. package/dist/actions/actions.d.ts +1 -35
  8. package/dist/actions/actions.js +32 -65
  9. package/dist/actions/appeared-text.js +3 -0
  10. package/dist/actions/danger-context.d.ts +2 -0
  11. package/dist/actions/danger-context.js +29 -3
  12. package/dist/actions/value-input.d.ts +1 -0
  13. package/dist/actions/value-input.js +11 -0
  14. package/dist/commands/commands.js +4 -0
  15. package/dist/connect-options.d.ts +1 -0
  16. package/dist/dom/a11y.js +264 -14
  17. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  18. package/dist/dom/addressing/coverage-markers.js +13 -0
  19. package/dist/dom/addressing/refs.js +2 -1
  20. package/dist/dom/addressing/testid-attr.d.ts +4 -0
  21. package/dist/dom/addressing/testid-attr.js +14 -0
  22. package/dist/dom/dom-ignore.d.ts +2 -0
  23. package/dist/dom/dom-ignore.js +15 -2
  24. package/dist/dom/query.d.ts +2 -1
  25. package/dist/dom/query.js +116 -27
  26. package/dist/dom/render-page.d.ts +12 -0
  27. package/dist/dom/render-page.js +135 -0
  28. package/dist/dom/snapshot.js +56 -7
  29. package/dist/observers/departure.js +51 -9
  30. package/dist/observers/dom.js +35 -18
  31. package/dist/observers/field.js +5 -21
  32. package/dist/observers/health.js +63 -2
  33. package/dist/observers/network-stream.d.ts +1 -1
  34. package/dist/observers/network-stream.js +3 -3
  35. package/dist/observers/network.js +57 -8
  36. package/dist/observers/perf.js +53 -1
  37. package/dist/observers/route.js +6 -5
  38. package/dist/observers/storage.js +6 -1
  39. package/dist/presenter/carousel/carousel.d.ts +5 -2
  40. package/dist/presenter/carousel/carousel.js +30 -8
  41. package/dist/presenter/carousel/panel-slides.d.ts +3 -4
  42. package/dist/presenter/carousel/panel-slides.js +48 -9
  43. package/dist/presenter/carousel/talk-card.d.ts +1 -1
  44. package/dist/presenter/chrome/log-kinds.d.ts +5 -0
  45. package/dist/presenter/chrome/log-kinds.js +1 -0
  46. package/dist/presenter/chrome/log-memory.d.ts +13 -0
  47. package/dist/presenter/chrome/log-memory.js +39 -0
  48. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
  49. package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
  50. package/dist/presenter/chrome/presenter-log.d.ts +12 -4
  51. package/dist/presenter/chrome/presenter-log.js +100 -10
  52. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  53. package/dist/presenter/chrome/presenter-morph.js +34 -0
  54. package/dist/presenter/chrome/presenter-report-copy.d.ts +28 -5
  55. package/dist/presenter/chrome/presenter-report-copy.js +40 -5
  56. package/dist/presenter/hud-telemetry.js +83 -3
  57. package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
  58. package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
  59. package/dist/presenter/icons/presenter-icons.d.ts +2 -0
  60. package/dist/presenter/icons/presenter-icons.js +2 -0
  61. package/dist/presenter/presenter-account-styles.js +3 -3
  62. package/dist/presenter/presenter-account.d.ts +31 -8
  63. package/dist/presenter/presenter-account.js +105 -15
  64. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  65. package/dist/presenter/presenter-agent-link.js +62 -0
  66. package/dist/presenter/presenter-chat-views.d.ts +43 -0
  67. package/dist/presenter/presenter-chat-views.js +622 -0
  68. package/dist/presenter/presenter-config.d.ts +5 -2
  69. package/dist/presenter/presenter-config.js +5 -3
  70. package/dist/presenter/presenter-controls.d.ts +3 -2
  71. package/dist/presenter/presenter-controls.js +163 -61
  72. package/dist/presenter/presenter-design-tokens.d.ts +1 -0
  73. package/dist/presenter/presenter-design-tokens.js +34 -0
  74. package/dist/presenter/presenter-dock-layout.js +1 -1
  75. package/dist/presenter/presenter-frame.d.ts +12 -0
  76. package/dist/presenter/presenter-frame.js +187 -0
  77. package/dist/presenter/presenter-harness-row.d.ts +67 -0
  78. package/dist/presenter/presenter-harness-row.js +209 -0
  79. package/dist/presenter/presenter-personas.d.ts +35 -0
  80. package/dist/presenter/presenter-personas.js +95 -0
  81. package/dist/presenter/presenter-plan.d.ts +10 -0
  82. package/dist/presenter/presenter-plan.js +186 -0
  83. package/dist/presenter/presenter-report-styles.js +46 -21
  84. package/dist/presenter/presenter-report.d.ts +2 -1
  85. package/dist/presenter/presenter-report.js +123 -20
  86. package/dist/presenter/presenter-settings-styles.js +18 -4
  87. package/dist/presenter/presenter-settings.d.ts +8 -2
  88. package/dist/presenter/presenter-settings.js +134 -33
  89. package/dist/presenter/presenter-shell-styles.js +48 -42
  90. package/dist/presenter/presenter-shell.d.ts +17 -4
  91. package/dist/presenter/presenter-shell.js +139 -79
  92. package/dist/presenter/presenter-styles.js +9 -3
  93. package/dist/presenter/presenter-workspace.js +10 -3
  94. package/dist/presenter/presenter.d.ts +6 -4
  95. package/dist/presenter/presenter.js +171 -22
  96. package/dist/presenter/tour/tour.d.ts +4 -1
  97. package/dist/presenter/tour/tour.js +6 -4
  98. package/dist/recorder/recorder.js +2 -2
  99. package/dist/registry/auto-testids.js +3 -4
  100. package/dist/registry/declared-platform.js +4 -0
  101. package/dist/reticle-inject.js +938 -494
  102. package/dist/reticle.d.ts +10 -1
  103. package/dist/reticle.js +109 -11
  104. package/dist/review/annotator-styles.d.ts +1 -1
  105. package/dist/review/annotator.d.ts +7 -0
  106. package/dist/review/annotator.js +20 -2
  107. package/dist/review/mark-anchor.js +2 -2
  108. package/dist/session-continuity.js +2 -1
  109. package/dist/storage-keys.d.ts +15 -0
  110. package/dist/storage-keys.js +21 -0
  111. package/dist/transport/transport.js +5 -3
  112. package/dist/transport/unreachable-message.d.ts +1 -1
  113. package/dist/transport/unreachable-message.js +3 -5
  114. package/package.json +3 -2
  115. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  116. package/dist/presenter/carousel/offer-card.js +0 -55
@@ -0,0 +1,186 @@
1
+ import { ScriptStatus } from '@reticlehq/core';
2
+ export const PLAN_ATTR = 'data-reticle-plan';
3
+ const CLOSE_ATTR = 'data-reticle-plan-close';
4
+ const JOURNEY_ATTR = 'data-reticle-plan-journey';
5
+ export const PLAN_HTML = `<div ${PLAN_ATTR} class="reticle-plan" hidden></div>`;
6
+ const TEXT = {
7
+ TITLE: 'Harness plan',
8
+ CLOSE: 'Hide the plan',
9
+ WAITS: 'waits on',
10
+ AT_ONCE: 'at once',
11
+ LANE: 'Lane',
12
+ };
13
+ const GLYPH = {
14
+ [ScriptStatus.PENDING]: '○',
15
+ [ScriptStatus.RUNNING]: '◐',
16
+ [ScriptStatus.PASSED]: '✓',
17
+ [ScriptStatus.FAILED]: '✗',
18
+ [ScriptStatus.BLOCKED]: '⊘',
19
+ [ScriptStatus.NOT_TAKEN]: '–',
20
+ [ScriptStatus.UNDECIDED]: '?',
21
+ };
22
+ export const PLAN_CSS = `
23
+ [data-reticle-page-panel="harness"] .reticle-plan{flex:none;margin:0 10px 8px;padding:8px 10px;border:1px solid var(--reticle-hud-border);border-radius:10px;background:rgba(255,255,255,.03);}
24
+ [data-reticle-page-panel="harness"] .reticle-plan[hidden]{display:none;}
25
+ [data-reticle-page-panel="harness"] .reticle-plan-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
26
+ [data-reticle-page-panel="harness"] .reticle-plan-head strong{color:var(--reticle-hud-text);font-size:var(--reticle-hud-size-sm);font-weight:600;}
27
+ [data-reticle-page-panel="harness"] .reticle-plan-head span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
28
+ [data-reticle-page-panel="harness"] .reticle-plan-close{flex:none;border:0;background:none;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:2px 4px;}
29
+ [data-reticle-page-panel="harness"] .reticle-plan-lanes{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow:auto;scrollbar-width:none;}
30
+ [data-reticle-page-panel="harness"] .reticle-plan-lanes::-webkit-scrollbar{display:none;}
31
+ [data-reticle-page-panel="harness"] .reticle-plan-lane{display:flex;gap:6px;align-items:flex-start;min-width:0;}
32
+ [data-reticle-page-panel="harness"] .reticle-plan-lane-name{flex:none;width:16px;padding-top:6px;font-size:11px;font-weight:600;text-align:center;color:var(--reticle-hud-text-muted);}
33
+ [data-reticle-page-panel="harness"] .reticle-plan-lane-journeys{display:flex;flex:1;min-width:0;flex-direction:column;gap:4px;}
34
+ [data-reticle-page-panel="harness"] .reticle-plan-journey{display:block;width:100%;text-align:left;border:1px solid transparent;border-radius:8px;padding:5px 7px;background:rgba(255,255,255,.04);color:var(--reticle-hud-text);font:inherit;font-size:var(--reticle-hud-size-xs);cursor:pointer;}
35
+ [data-reticle-page-panel="harness"] .reticle-plan-journey[data-status="running"]{border-color:var(--reticle-c-active);box-shadow:0 0 12px -4px var(--reticle-c-active);}
36
+ [data-reticle-page-panel="harness"] .reticle-plan-journey[data-status="blocked"],[data-reticle-page-panel="harness"] .reticle-plan-journey[data-status="not-taken"]{opacity:.55;}
37
+ [data-reticle-page-panel="harness"] .reticle-plan-row{display:flex;gap:6px;align-items:baseline;min-width:0;}
38
+ [data-reticle-page-panel="harness"] .reticle-plan-row b{flex:1;min-width:0;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
39
+ [data-reticle-page-panel="harness"] .reticle-plan-glyph{flex:none;width:12px;text-align:center;}
40
+ [data-reticle-page-panel="harness"] [data-status="running"]>.reticle-plan-row>.reticle-plan-glyph,[data-reticle-page-panel="harness"] .reticle-plan-step[data-status="running"]>.reticle-plan-glyph{color:var(--reticle-c-active);animation:reticle-flow-pulse 1.2s ease-in-out infinite;}
41
+ [data-reticle-page-panel="harness"] [data-status="passed"]>.reticle-plan-row>.reticle-plan-glyph,[data-reticle-page-panel="harness"] .reticle-plan-step[data-status="passed"]>.reticle-plan-glyph{color:#4ade80;}
42
+ [data-reticle-page-panel="harness"] [data-status="failed"]>.reticle-plan-row>.reticle-plan-glyph,[data-reticle-page-panel="harness"] .reticle-plan-step[data-status="failed"]>.reticle-plan-glyph{color:#f87171;}
43
+ [data-reticle-page-panel="harness"] .reticle-plan-meta{display:block;margin:2px 0 0 18px;color:var(--reticle-hud-text-muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
44
+ [data-reticle-page-panel="harness"] .reticle-plan-steps{margin:4px 0 0 18px;display:flex;flex-direction:column;gap:2px;}
45
+ [data-reticle-page-panel="harness"] .reticle-plan-step{display:flex;gap:6px;color:var(--reticle-hud-text-muted);font-size:11px;min-width:0;}
46
+ [data-reticle-page-panel="harness"] .reticle-plan-step span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
47
+ `;
48
+ const STATUSES = new Set(Object.values(ScriptStatus));
49
+ function statusOf(value) {
50
+ return 'string' === typeof value && STATUSES.has(value)
51
+ ? value
52
+ : ScriptStatus.PENDING;
53
+ }
54
+ function textOf(value) {
55
+ return 'string' === typeof value ? value : '';
56
+ }
57
+ function listOf(value) {
58
+ return Array.isArray(value)
59
+ ? value.filter((v) => 'object' === typeof v && null !== v)
60
+ : [];
61
+ }
62
+ export function parsePlanView(args) {
63
+ const lanes = listOf(args['lanes']).map((lane) => ({
64
+ id: textOf(lane['id']),
65
+ journeys: listOf(lane['journeys']).map((j) => ({
66
+ id: textOf(j['id']),
67
+ title: textOf(j['title']),
68
+ ...('string' === typeof j['persona'] ? { persona: j['persona'] } : {}),
69
+ waitsOn: Array.isArray(j['waitsOn']) ? j['waitsOn'].map(textOf) : [],
70
+ status: statusOf(j['status']),
71
+ steps: listOf(j['steps']).map((s) => ({
72
+ label: textOf(s['label']),
73
+ status: statusOf(s['status']),
74
+ })),
75
+ })),
76
+ }));
77
+ if (0 === lanes.length)
78
+ return undefined;
79
+ const parallel = 'number' === typeof args['parallel'] ? args['parallel'] : 1;
80
+ return { parallel, lanes };
81
+ }
82
+ function el(tag, className, text) {
83
+ const node = document.createElement(tag);
84
+ node.className = className;
85
+ if (text !== undefined)
86
+ node.textContent = text;
87
+ return node;
88
+ }
89
+ export class PlanBoard {
90
+ #host;
91
+ #view;
92
+ #dismissed = false;
93
+ #toggled = new Map();
94
+ mount(root) {
95
+ this.#host = root.querySelector(`[${PLAN_ATTR}]`) ?? undefined;
96
+ this.#host?.addEventListener('click', (event) => {
97
+ const target = event.target;
98
+ if (!(target instanceof Element))
99
+ return;
100
+ if (null !== target.closest(`[${CLOSE_ATTR}]`)) {
101
+ this.#dismissed = true;
102
+ this.#paint();
103
+ return;
104
+ }
105
+ const card = target.closest(`[${JOURNEY_ATTR}]`);
106
+ const key = card?.getAttribute(JOURNEY_ATTR);
107
+ if (null === key || key === undefined)
108
+ return;
109
+ this.#toggled.set(key, 'true' !== card?.getAttribute('aria-expanded'));
110
+ this.#paint();
111
+ });
112
+ this.#paint();
113
+ }
114
+ paint(view) {
115
+ const fresh = view.lanes.every((lane) => lane.journeys.every((j) => ScriptStatus.PENDING === j.status));
116
+ if (fresh) {
117
+ this.#dismissed = false;
118
+ this.#toggled.clear();
119
+ }
120
+ this.#view = view;
121
+ this.#paint();
122
+ }
123
+ #paint() {
124
+ const host = this.#host;
125
+ const view = this.#view;
126
+ if (host === undefined)
127
+ return;
128
+ host.replaceChildren();
129
+ host.hidden = view === undefined || this.#dismissed;
130
+ if (view === undefined || this.#dismissed)
131
+ return;
132
+ const journeys = view.lanes.flatMap((lane) => lane.journeys);
133
+ const done = journeys.filter((j) => ScriptStatus.PASSED === j.status).length;
134
+ const head = el('div', 'reticle-plan-head');
135
+ head.append(el('strong', '', TEXT.TITLE), el('span', '', `${String(done)} of ${String(journeys.length)} passed · ${String(view.lanes.length)} lane(s)` +
136
+ (1 < view.parallel ? `, ${String(view.parallel)} ${TEXT.AT_ONCE}` : '')));
137
+ const close = el('button', 'reticle-plan-close', '×');
138
+ close.setAttribute('type', 'button');
139
+ close.setAttribute(CLOSE_ATTR, '');
140
+ close.setAttribute('aria-label', TEXT.CLOSE);
141
+ close.setAttribute('title', TEXT.CLOSE);
142
+ head.append(close);
143
+ const lanes = el('div', 'reticle-plan-lanes');
144
+ for (const lane of view.lanes) {
145
+ const row = el('div', 'reticle-plan-lane');
146
+ const name = el('span', 'reticle-plan-lane-name', lane.id);
147
+ name.setAttribute('title', `${TEXT.LANE} ${lane.id}`);
148
+ const journeys = el('div', 'reticle-plan-lane-journeys');
149
+ for (const journey of lane.journeys)
150
+ journeys.append(this.#card(lane.id, journey));
151
+ row.append(name, journeys);
152
+ lanes.append(row);
153
+ }
154
+ host.append(head, lanes);
155
+ }
156
+ #card(laneId, journey) {
157
+ const key = `${laneId}/${journey.id}`;
158
+ const open = this.#toggled.get(key) ??
159
+ (ScriptStatus.RUNNING === journey.status || ScriptStatus.FAILED === journey.status);
160
+ const card = el('button', 'reticle-plan-journey');
161
+ card.setAttribute('type', 'button');
162
+ card.setAttribute(JOURNEY_ATTR, key);
163
+ card.setAttribute('data-status', journey.status);
164
+ card.setAttribute('aria-expanded', String(open));
165
+ const row = el('span', 'reticle-plan-row');
166
+ row.append(el('span', 'reticle-plan-glyph', GLYPH[journey.status]), el('b', '', journey.title));
167
+ card.append(row);
168
+ const meta = [
169
+ journey.persona,
170
+ 0 < journey.waitsOn.length ? `${TEXT.WAITS} ${journey.waitsOn.join(', ')}` : undefined,
171
+ ].filter((part) => part !== undefined && 0 < part.length);
172
+ if (0 < meta.length)
173
+ card.append(el('span', 'reticle-plan-meta', meta.join(' · ')));
174
+ if (open && 0 < journey.steps.length) {
175
+ const steps = el('span', 'reticle-plan-steps');
176
+ for (const step of journey.steps) {
177
+ const line = el('span', 'reticle-plan-step');
178
+ line.setAttribute('data-status', step.status);
179
+ line.append(el('span', 'reticle-plan-glyph', GLYPH[step.status]), el('span', '', step.label));
180
+ steps.append(line);
181
+ }
182
+ card.append(steps);
183
+ }
184
+ return card;
185
+ }
186
+ }
@@ -26,17 +26,17 @@ export const REPORT_CSS = `
26
26
  [${REPORT_PANEL_ATTR}] .reticle-report-scope{
27
27
  margin-left:auto;padding:3px 9px;border-radius:999px;cursor:pointer;
28
28
  border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04);
29
- color:var(--reticle-muted);font:inherit;font-size:10.5px;}
29
+ color:var(--reticle-muted);font:inherit;font-size:11px;}
30
30
  [${REPORT_PANEL_ATTR}] .reticle-report-scope:hover{background:rgba(255,255,255,.08);color:var(--reticle-fg);}
31
31
  [${REPORT_PANEL_ATTR}] .reticle-report-close{
32
32
  flex:none;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;
33
33
  border:none;background:transparent;color:var(--reticle-faint);cursor:pointer;line-height:0;}
34
34
  [${REPORT_PANEL_ATTR}] .reticle-report-close svg{display:block;fill:none;stroke:currentColor;stroke-width:1.6;}
35
35
  [${REPORT_PANEL_ATTR}] .reticle-report-body{
36
- min-height:0;overflow-y:auto;padding:12px 14px 6px;
36
+ flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:12px 14px 6px;
37
37
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent;}
38
38
  [${REPORT_PANEL_ATTR}] .reticle-report-empty{
39
- margin:0;padding:22px 6px;text-align:center;color:var(--reticle-faint);font-size:11.5px;}
39
+ margin:0;padding:22px 6px;text-align:center;color:var(--reticle-faint);font-size:12px;}
40
40
  /* The streak sits above the hero, as a flame and a number: it is the one stat that is about
41
41
  TODAY, so it reads first and never competes with the count it sits over. */
42
42
  [${REPORT_PANEL_ATTR}] .reticle-report-streak{
@@ -45,14 +45,36 @@ export const REPORT_CSS = `
45
45
  border:1px solid color-mix(in srgb,var(--reticle-c-active) 22%,transparent);
46
46
  font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;}
47
47
  [${REPORT_PANEL_ATTR}] .reticle-report-streak-label{
48
- color:var(--reticle-faint);font-size:9.5px;font-weight:400;letter-spacing:.04em;text-transform:uppercase;}
48
+ color:var(--reticle-faint);font-size:11px;font-weight:400;}
49
49
  [${REPORT_PANEL_ATTR}] .reticle-report-hero{display:flex;flex-direction:column;gap:2px;margin-bottom:10px;}
50
50
  [${REPORT_PANEL_ATTR}] .reticle-report-hero-value{
51
51
  font-size:34px;font-weight:700;line-height:1;letter-spacing:-.02em;color:var(--reticle-c-active);}
52
- [${REPORT_PANEL_ATTR}] .reticle-report-hero-label{color:var(--reticle-muted);font-size:11.5px;}
52
+ [${REPORT_PANEL_ATTR}] .reticle-report-hero-label{color:var(--reticle-muted);font-size:12px;}
53
53
  [${REPORT_PANEL_ATTR}] .reticle-report-verdicts{display:flex;gap:6px;margin-bottom:12px;}
54
+ [${REPORT_PANEL_ATTR}] .reticle-sync-status{font-size:11px;color:var(--reticle-faint);font-variant-numeric:tabular-nums;white-space:nowrap;}
55
+ [${REPORT_PANEL_ATTR}] .reticle-sync-status[data-reticle-sync-status="refused"]{color:var(--reticle-c-error,#f87171);}
56
+ [${REPORT_PANEL_ATTR}] .reticle-report-coverage{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin-bottom:12px;padding:8px 10px;border:1px solid var(--reticle-hud-border);border-radius:var(--reticle-hud-radius-sm,8px);}
57
+ [${REPORT_PANEL_ATTR}] .reticle-report-coverage-value{font-size:18px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--reticle-hud-text);}
58
+ [${REPORT_PANEL_ATTR}] .reticle-report-coverage-levels{display:flex;flex-wrap:wrap;gap:4px 10px;width:100%;font-size:11px;color:var(--reticle-faint);font-variant-numeric:tabular-nums;}
59
+ [${REPORT_PANEL_ATTR}] .reticle-report-instrumentation{margin-bottom:12px;padding:10px;border:1px solid var(--reticle-hud-border);border-radius:var(--reticle-hud-radius-md);}
60
+ [${REPORT_PANEL_ATTR}] .reticle-report-instrumentation-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;}
61
+ [${REPORT_PANEL_ATTR}] .reticle-report-instrumentation-head .reticle-report-section{margin-bottom:0;}
62
+ [${REPORT_PANEL_ATTR}] .reticle-report-instrumentation-value{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--reticle-hud-text);}
63
+ [${REPORT_PANEL_ATTR}] .reticle-report-caps{display:grid;grid-template-columns:1fr 1fr;gap:4px 10px;margin:8px 0 0;padding:0;list-style:none;}
64
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px;min-width:0;font-size:11px;line-height:1.4;color:var(--reticle-muted);}
65
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap-mark{flex:none;width:10px;text-align:center;}
66
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap-name{flex:1;min-width:0;}
67
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap[data-state="seen"] .reticle-report-cap-mark{color:#86efac;}
68
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap[data-state="missing"]{grid-column:1/-1;color:var(--reticle-hud-text);}
69
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap[data-state="missing"] .reticle-report-cap-mark{color:#fca5a5;}
70
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap[data-state="unseen"]{color:var(--reticle-faint);}
71
+ [${REPORT_PANEL_ATTR}] .reticle-report-cap-cost{flex-basis:100%;padding-left:16px;color:var(--reticle-faint);}
72
+ [${REPORT_PANEL_ATTR}] .reticle-report-caps-note{display:block;margin-top:6px;color:var(--reticle-faint);font-size:11px;}
73
+ [${REPORT_PANEL_ATTR}] .reticle-report-copy-prompt{display:block;width:100%;margin-top:10px;padding:6px 10px;border:1px solid color-mix(in srgb,var(--reticle-hud-brand) 50%,transparent);
74
+ border-radius:var(--reticle-hud-radius-sm);background:var(--reticle-hud-brand-soft);color:var(--reticle-hud-brand);font:inherit;font-size:12px;font-weight:600;cursor:pointer;}
75
+ [${REPORT_PANEL_ATTR}] .reticle-report-copy-prompt:hover{filter:brightness(1.15);}
54
76
  [${REPORT_PANEL_ATTR}] .reticle-report-verdict{
55
- flex:1;padding:5px 8px;border-radius:8px;font-size:10.5px;text-align:center;
77
+ flex:1;padding:5px 8px;border-radius:8px;font-size:11px;text-align:center;
56
78
  background:rgba(255,255,255,.05);color:var(--reticle-muted);}
57
79
  [${REPORT_PANEL_ATTR}] .reticle-report-verdict[data-kind="fail"]{color:#fca5a5;}
58
80
  [${REPORT_PANEL_ATTR}] .reticle-report-verdict[data-kind="unknown"]{color:#fcd34d;}
@@ -62,11 +84,11 @@ export const REPORT_CSS = `
62
84
  display:flex;flex-direction:column;gap:2px;padding:8px;border-radius:10px;
63
85
  background:rgba(255,255,255,.04);}
64
86
  [${REPORT_PANEL_ATTR}] .reticle-report-value{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums;}
65
- [${REPORT_PANEL_ATTR}] .reticle-report-label{color:var(--reticle-faint);font-size:9.5px;line-height:1.3;}
87
+ [${REPORT_PANEL_ATTR}] .reticle-report-label{color:var(--reticle-faint);font-size:11px;line-height:1.3;}
66
88
  /* An estimate is labelled as one, and its label carries what it is measured against. */
67
89
  [${REPORT_PANEL_ATTR}] .reticle-report-basis{
68
90
  display:block;margin-top:2px;color:var(--reticle-c-active);opacity:.8;
69
- font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;cursor:help;}
91
+ font-size:11px;letter-spacing:.06em;text-transform:uppercase;cursor:help;}
70
92
  [${REPORT_PANEL_ATTR}] .reticle-report-defects-wrap{margin-top:14px;}
71
93
  /* The heading and its push control on one line, so the button reads as belonging to this section
72
94
  rather than floating above the list. */
@@ -82,9 +104,9 @@ export const REPORT_CSS = `
82
104
  [${REPORT_PANEL_ATTR}] .reticle-report-defect-title{
83
105
  font-size:11px;line-height:1.35;overflow-wrap:anywhere;}
84
106
  [${REPORT_PANEL_ATTR}] .reticle-report-defect-detail{
85
- color:var(--reticle-faint);font-size:10px;line-height:1.35;overflow-wrap:anywhere;}
107
+ color:var(--reticle-faint);font-size:11px;line-height:1.35;overflow-wrap:anywhere;}
86
108
  [${REPORT_PANEL_ATTR}] .reticle-report-defect-source{
87
- color:var(--reticle-faint);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
109
+ color:var(--reticle-faint);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
88
110
  overflow-wrap:anywhere;}
89
111
  .reticle-report-defect-link{flex:none;display:inline-flex;align-items:center;justify-content:center;
90
112
  width:20px;height:20px;border-radius:6px;color:var(--reticle-faint);pointer-events:auto;
@@ -93,17 +115,19 @@ export const REPORT_CSS = `
93
115
  .reticle-report-defect-link:focus-visible{opacity:1;}
94
116
  .reticle-report-defect-link:hover{color:var(--reticle-fg);background:rgba(255,255,255,.07);}
95
117
  [${REPORT_PANEL_ATTR}] .reticle-report-defects-more{
96
- display:inline-block;margin-top:8px;font-size:10px;color:var(--reticle-c-active);
118
+ display:inline-block;margin-top:8px;font-size:11px;color:var(--reticle-c-active);
97
119
  text-decoration:none;}
98
120
  [${REPORT_PANEL_ATTR}] .reticle-report-defects-more:hover{text-decoration:underline;}
121
+ [${REPORT_PANEL_ATTR}] .reticle-report-defects-all{display:inline-block;margin-top:8px;border:0;padding:0;background:none;cursor:pointer;font:inherit;font-size:11px;font-weight:500;color:var(--reticle-c-active);}
122
+ [${REPORT_PANEL_ATTR}] .reticle-report-defects-all:hover{text-decoration:underline;}
99
123
  [${REPORT_PANEL_ATTR}] .reticle-report-local-only{
100
124
  margin:14px 0 0;padding-top:10px;border-top:1px solid var(--reticle-line);
101
- color:var(--reticle-faint);font-size:10px;line-height:1.45;}
125
+ color:var(--reticle-faint);font-size:11px;line-height:1.45;}
102
126
  [${REPORT_PANEL_ATTR}] .reticle-report-local-only code{
103
127
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--reticle-c-active);}
104
128
  [${REPORT_PANEL_ATTR}] .reticle-report-chart-wrap{margin-top:12px;}
105
129
  [${REPORT_PANEL_ATTR}] .reticle-report-section{
106
- display:block;margin-bottom:6px;color:var(--reticle-faint);font-size:9.5px;
130
+ display:block;margin-bottom:6px;color:var(--reticle-faint);font-size:11px;
107
131
  letter-spacing:.06em;text-transform:uppercase;}
108
132
  /* Thirty fixed slots, not a flex row: one recorded day is one thin bar on a month's axis, and a
109
133
  single stretched block would read as "a month of solid work" on the day you install it. */
@@ -113,19 +137,20 @@ export const REPORT_CSS = `
113
137
  min-width:2px;border-radius:2px 2px 0 0;
114
138
  background:color-mix(in srgb,var(--reticle-c-active) 55%,transparent);}
115
139
  [${REPORT_PANEL_ATTR}] .reticle-report-bar[data-hot="1"]{background:#f87171;}
140
+ /* Sharing is a quiet row under the record, never the loudest thing on the panel. */
116
141
  [${REPORT_PANEL_ATTR}] .reticle-report-foot{
117
- flex:none;display:flex;flex-wrap:wrap;gap:6px;padding:10px 14px 8px;
142
+ flex:none;display:flex;flex-wrap:nowrap;align-items:center;gap:2px;padding:6px 10px 2px 14px;
118
143
  border-top:1px solid rgba(255,255,255,.06);}
144
+ [${REPORT_PANEL_ATTR}] .reticle-report-share-label{margin-right:4px;color:var(--reticle-faint);font-size:11px;}
119
145
  [${REPORT_PANEL_ATTR}] .reticle-report-share{
120
- padding:5px 10px;border-radius:8px;cursor:pointer;font:inherit;font-size:11px;
121
- border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04);color:var(--reticle-muted);}
122
- [${REPORT_PANEL_ATTR}] .reticle-report-share:hover{background:rgba(255,255,255,.09);color:var(--reticle-fg);}
123
- [${REPORT_PANEL_ATTR}] .reticle-report-refer{
124
- border-color:color-mix(in srgb,var(--reticle-c-active) 45%,transparent);color:var(--reticle-fg);}
146
+ flex:none;padding:3px 6px;border:0;border-radius:6px;cursor:pointer;font:inherit;font-size:11px;
147
+ background:transparent;color:var(--reticle-muted);white-space:nowrap;}
148
+ [${REPORT_PANEL_ATTR}] .reticle-report-share:hover{background:rgba(255,255,255,.08);color:var(--reticle-fg);}
149
+ [${REPORT_PANEL_ATTR}] .reticle-report-refer{margin-left:auto;color:var(--reticle-hud-brand);}
125
150
  [${REPORT_PANEL_ATTR}] .reticle-report-links{
126
- flex:none;display:flex;gap:12px;padding:0 14px 12px;}
151
+ flex:none;display:flex;gap:12px;padding:2px 14px 10px;}
127
152
  [${REPORT_PANEL_ATTR}] .reticle-report-links a{
128
- color:var(--reticle-faint);font-size:10.5px;text-decoration:none;}
153
+ color:var(--reticle-faint);font-size:11px;text-decoration:none;}
129
154
  [${REPORT_PANEL_ATTR}] .reticle-report-links a:hover{color:var(--reticle-fg);text-decoration:underline;}
130
155
 
131
156
  /* Who this record belongs to, and the push-now control. Tokens only — no literal colours. */
@@ -1,6 +1,7 @@
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, projectName?: string): string;
3
+ export declare function instrumentationHtml(instrumentation: Readonly<Record<string, unknown>> | undefined): string;
4
+ export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState, projectName?: string, coverage?: Readonly<Record<string, number>>, sync?: Readonly<Record<string, unknown>>, instrumentation?: Readonly<Record<string, unknown>>): string;
4
5
  interface ReportHost {
5
6
  onBeforeOpen?: () => void;
6
7
  onSyncNow?: () => void;
@@ -4,8 +4,9 @@ import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
4
4
  import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
5
5
  import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, SYNC_BTN_ATTR, accountControlHtml, syncButtonHtml, } from './presenter-account.js';
6
6
  import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
7
- import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './chrome/presenter-report-copy.js';
7
+ import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, COVERAGE_LEVELS, CAPABILITY_WORDS, } from './chrome/presenter-report-copy.js';
8
8
  const SCOPE_ATTR = 'data-reticle-report-scope';
9
+ const SCOPE = { PROJECT: 'project', MACHINE: 'machine' };
9
10
  const SHARE_X_ATTR = 'data-reticle-share-x';
10
11
  const SHARE_IN_ATTR = 'data-reticle-share-in';
11
12
  const SHARE_COPY_ATTR = 'data-reticle-share-copy';
@@ -17,13 +18,14 @@ export function reportPanelHtml() {
17
18
  <div class="reticle-report-inner">
18
19
  <div class="reticle-report-head">
19
20
  <span class="reticle-report-title">${REPORT_TEXT.TITLE}</span>
20
- <button type="button" ${SCOPE_ATTR} class="reticle-report-scope" aria-pressed="false">${REPORT_TEXT.PROJECT}</button>
21
+ <span class="reticle-segmented reticle-report-scopes" role="group" aria-label="${REPORT_TEXT.SCOPE_LABEL}"><button type="button" ${SCOPE_ATTR}="${SCOPE.PROJECT}" aria-pressed="true">${REPORT_TEXT.PROJECT}</button><button type="button" ${SCOPE_ATTR}="${SCOPE.MACHINE}" aria-pressed="false" title="${REPORT_TEXT.GLOBAL_TITLE}">${REPORT_TEXT.GLOBAL}</button></span>
21
22
  <button type="button" ${REPORT_CLOSE_ATTR} class="reticle-report-close" title="Close" aria-label="Close impact">${close}</button>
22
23
  </div>
23
24
  <div class="reticle-report-body" data-reticle-report-body></div>
24
- <div class="reticle-report-foot">
25
- <button type="button" ${SHARE_X_ATTR} class="reticle-report-share">Post on X</button>
26
- <button type="button" ${SHARE_IN_ATTR} class="reticle-report-share">LinkedIn</button>
25
+ <div class="reticle-report-foot" role="group" aria-label="${REPORT_TEXT.SHARE_LABEL}">
26
+ <span class="reticle-report-share-label">${REPORT_TEXT.SHARE_LABEL}</span>
27
+ <button type="button" ${SHARE_X_ATTR} class="reticle-report-share">${REPORT_TEXT.POST_X}</button>
28
+ <button type="button" ${SHARE_IN_ATTR} class="reticle-report-share">${REPORT_TEXT.POST_IN}</button>
27
29
  <button type="button" ${SHARE_COPY_ATTR} class="reticle-report-share">${REPORT_TEXT.COPY}</button>
28
30
  <button type="button" ${REFER_ATTR} class="reticle-report-share reticle-report-refer">${REPORT_TEXT.REFER}</button>
29
31
  </div>
@@ -76,20 +78,111 @@ function defects(scope, dashboardUrl) {
76
78
  return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}${rowLink}</li>`;
77
79
  })
78
80
  .join('');
81
+ const hidden = scope.counts.failed > list.length;
79
82
  const more = !linked
80
- ? ''
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>`;
83
+ ? !hidden
84
+ ? ''
85
+ : `<button type="button" data-reticle-defects-all ${ACCOUNT_SIGNIN_ATTR} class="reticle-report-defects-all" title="${REPORT_TEXT.DEFECTS_SEE_ALL_TITLE}">${REPORT_TEXT.DEFECTS_SEE_ALL} (${String(scope.counts.failed)}) →</button>`
86
+ : `<a class="reticle-report-defects-more" data-reticle-link="dashboard" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener">${REPORT_TEXT.DEFECTS_MORE}${hidden ? ` (${String(scope.counts.failed)})` : ''}</a>`;
82
87
  const sync = syncButtonHtml(dashboardUrl);
83
88
  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
89
  }
85
- export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
90
+ function syncStatusHtml(sync) {
91
+ if (sync === undefined)
92
+ return '';
93
+ const num = (key) => ('number' === typeof sync[key] ? sync[key] : 0);
94
+ const status = sync['status'];
95
+ const keyRefused = 'string' === typeof sync['keyRefused'] ? sync['keyRefused'] : undefined;
96
+ const label = keyRefused !== undefined
97
+ ? REPORT_TEXT.KEY_REFUSED
98
+ : 'refused' === status
99
+ ? `${String(num('refused'))} refused`
100
+ : 'pending' === status
101
+ ? `${String(num('pending'))} waiting`
102
+ : 'on-platform' === status
103
+ ? `Synced · ${String(num('onPlatform'))}`
104
+ : 'local-only' === status
105
+ ? `${String(num('pending'))} ${REPORT_TEXT.NOT_SENT}`
106
+ : undefined;
107
+ if (label === undefined)
108
+ return '';
109
+ const said = keyRefused !== undefined
110
+ ? keyRefused
111
+ : 'local-only' === status
112
+ ? REPORT_TEXT.NOT_SENT_HELP
113
+ : 'string' === typeof sync['said']
114
+ ? sync['said']
115
+ : '';
116
+ const runUrl = sync['runUrl'];
117
+ const seeRun = 'string' === typeof runUrl && isSafeDashboardUrl(runUrl)
118
+ ? ` <a class="reticle-sync-run" data-reticle-link="run" href="${esc(runUrl)}" target="_blank" rel="noreferrer noopener">${REPORT_TEXT.SEE_RUN}</a>`
119
+ : '';
120
+ return `<span class="reticle-sync-status" data-reticle-sync-status="${esc(String(status))}" title="${esc(said)}">${label}</span>${seeRun}`;
121
+ }
122
+ const capabilityWords = (name) => 'string' === typeof name ? esc(CAPABILITY_WORDS[name] ?? name) : undefined;
123
+ function capabilityItem(state, name, cost = '') {
124
+ const mark = 'seen' === state ? '✓' : 'missing' === state ? '✕' : '○';
125
+ const why = '' === cost ? '' : `<span class="reticle-report-cap-cost">${cost}</span>`;
126
+ return `<li class="reticle-report-cap" data-state="${state}"><span class="reticle-report-cap-mark" aria-hidden="true">${mark}</span><span class="reticle-report-cap-name">${name}</span>${why}</li>`;
127
+ }
128
+ export function instrumentationHtml(instrumentation) {
129
+ if (instrumentation === undefined)
130
+ return '';
131
+ const covered = instrumentation['covered'];
132
+ const total = instrumentation['total'];
133
+ if ('number' !== typeof covered || 'number' !== typeof total)
134
+ return '';
135
+ const list = (key) => {
136
+ const value = instrumentation[key];
137
+ return Array.isArray(value) ? value : [];
138
+ };
139
+ const seen = list('seen').flatMap((n) => {
140
+ const name = capabilityWords(n);
141
+ return name === undefined ? [] : [capabilityItem('seen', name)];
142
+ });
143
+ const missing = list('missing').flatMap((m) => {
144
+ if ('object' !== typeof m || null === m)
145
+ return [];
146
+ const gap = m;
147
+ const name = capabilityWords(gap['capability']);
148
+ const cost = 'string' === typeof gap['cost'] ? esc(gap['cost']) : '';
149
+ return name === undefined ? [] : [capabilityItem('missing', name, cost)];
150
+ });
151
+ const unseen = list('notSeenYet').flatMap((n) => {
152
+ const name = capabilityWords(n);
153
+ return name === undefined ? [] : [capabilityItem('unseen', name)];
154
+ });
155
+ const items = [...missing, ...seen, ...unseen];
156
+ const caps = 0 === items.length
157
+ ? ''
158
+ : `<ul class="reticle-report-caps">${items.join('')}</ul>${0 === unseen.length ? '' : `<span class="reticle-report-caps-note">○ ${REPORT_TEXT.NOT_SEEN_YET}</span>`}`;
159
+ const copy = 'string' === typeof instrumentation['prompt']
160
+ ? `<button type="button" class="reticle-report-copy-prompt" ${COPY_PROMPT_ATTR}>${REPORT_TEXT.COPY_PROMPT}</button>`
161
+ : '';
162
+ return `<div class="reticle-report-instrumentation" title="${REPORT_TEXT.INSTRUMENTATION_HELP}"><div class="reticle-report-instrumentation-head"><span class="reticle-report-section">${REPORT_TEXT.INSTRUMENTATION}</span><span class="reticle-report-instrumentation-value">${REPORT_TEXT.covered(Math.round(covered), Math.round(total))}</span></div>${caps}${copy}</div>`;
163
+ }
164
+ const COPY_PROMPT_ATTR = 'data-reticle-copy-prompt';
165
+ function coverageHtml(coverage) {
166
+ if (coverage === undefined)
167
+ return '';
168
+ const known = COVERAGE_LEVELS.filter((l) => 'number' === typeof coverage[l.key]);
169
+ if (0 === known.length)
170
+ return '';
171
+ const pct = (key) => `${String(Math.round(coverage[key] ?? 0))}%`;
172
+ const head = 'number' === typeof coverage['proved'] ? pct('proved') : pct(known[0]?.key ?? '');
173
+ const rows = known
174
+ .map((l) => `<span class="reticle-report-coverage-level">${pct(l.key)} ${l.label}</span>`)
175
+ .join('');
176
+ return `<div class="reticle-report-coverage" title="${REPORT_TEXT.COVERAGE_HELP}"><span class="reticle-report-section">${REPORT_TEXT.COVERAGE}</span><span class="reticle-report-coverage-value">${head}</span><div class="reticle-report-coverage-levels">${rows}</div></div>`;
177
+ }
178
+ export function reportBodyHtml(scope, dashboardUrl, account, projectName, coverage, sync, instrumentation) {
86
179
  const c = scope.counts;
87
180
  if (0 === c.calls)
88
181
  return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
89
182
  const streak = scope.records.streakDays > 0
90
183
  ? `<span class="reticle-report-streak" title="Consecutive days with at least one verdict">🔥 ${String(scope.records.streakDays)} <span class="reticle-report-streak-label">${REPORT_TEXT.STREAK}</span></span>`
91
184
  : '';
92
- const hero = `<div class="reticle-report-hero"><span class="reticle-report-hero-value">${compactNumber(c.failed)}</span><span class="reticle-report-hero-label">${REPORT_TEXT.HERO_DEFECTS}</span></div>`;
185
+ const hero = `<div class="reticle-report-hero"><span class="reticle-report-hero-value">${compactNumber(c.failed)}</span><span class="reticle-report-hero-label">${REPORT_TEXT.heroDefects(c.failed)}</span></div>`;
93
186
  const verdicts = `<div class="reticle-report-verdicts" title="${REPORT_TEXT.UNKNOWN_HELP}">
94
187
  <span class="reticle-report-verdict" data-kind="pass">${compactNumber(c.passed)} ${REPORT_TEXT.PASSED}</span>
95
188
  <span class="reticle-report-verdict" data-kind="fail">${compactNumber(c.failed)} ${REPORT_TEXT.FAILED}</span>
@@ -105,8 +198,8 @@ export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
105
198
  card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
106
199
  card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
107
200
  ].join('');
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)}`;
201
+ const identity = `<div class="reticle-report-identity">${accountControlHtml(account, { dashboardUrl, projectName, verdicts: c.verdicts, defects: c.failed })}${syncButtonHtml(dashboardUrl)}${syncStatusHtml(sync)}</div>`;
202
+ return `<div class="reticle-report-top">${streak}${identity}</div>${hero}${verdicts}${instrumentationHtml(instrumentation)}${coverageHtml(coverage)}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
110
203
  }
111
204
  function localOnly(scope, dashboardUrl, account) {
112
205
  if (dashboardUrl !== undefined)
@@ -140,14 +233,17 @@ export class PresenterReport {
140
233
  e.stopPropagation();
141
234
  this.close();
142
235
  });
143
- const scopeBtn = root.querySelector(`[${SCOPE_ATTR}]`);
144
- scopeBtn?.addEventListener('click', (e) => {
145
- e.stopPropagation();
146
- this.#global = !this.#global;
147
- scopeBtn.setAttribute('aria-pressed', this.#global ? 'true' : 'false');
148
- scopeBtn.textContent = this.#global ? REPORT_TEXT.GLOBAL : REPORT_TEXT.PROJECT;
149
- this.#paint();
150
- });
236
+ const scopeBtns = root.querySelectorAll(`[${SCOPE_ATTR}]`);
237
+ for (const btn of scopeBtns) {
238
+ btn.addEventListener('click', (e) => {
239
+ e.stopPropagation();
240
+ this.#global = SCOPE.MACHINE === btn.getAttribute(SCOPE_ATTR);
241
+ for (const other of scopeBtns) {
242
+ other.setAttribute('aria-pressed', other === btn ? 'true' : 'false');
243
+ }
244
+ this.#paint();
245
+ });
246
+ }
151
247
  root.querySelector(`[${SHARE_X_ATTR}]`)?.addEventListener('click', (e) => {
152
248
  e.stopPropagation();
153
249
  this.#openShare(buildXShareUrl(this.shareText()));
@@ -175,6 +271,13 @@ export class PresenterReport {
175
271
  void this.#copy(ACCOUNT_TEXT.SIGNIN_COMMAND, signin);
176
272
  return;
177
273
  }
274
+ const copyPrompt = target.closest(`[${COPY_PROMPT_ATTR}]`);
275
+ const prompt = this.#snapshot?.instrumentation?.['prompt'];
276
+ if (copyPrompt !== null && 'string' === typeof prompt) {
277
+ e.stopPropagation();
278
+ void this.#copy(prompt, copyPrompt);
279
+ return;
280
+ }
178
281
  const sync = target.closest(`[${SYNC_BTN_ATTR}]`);
179
282
  if (sync !== null) {
180
283
  e.stopPropagation();
@@ -237,7 +340,7 @@ export class PresenterReport {
237
340
  this.#body.innerHTML =
238
341
  scope === undefined
239
342
  ? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
240
- : reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName);
343
+ : reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName, this.#global ? undefined : this.#snapshot?.coverage, this.#global ? undefined : this.#snapshot?.sync, this.#global ? undefined : this.#snapshot?.instrumentation);
241
344
  }
242
345
  #openShare(url) {
243
346
  window.open(url, '_blank', 'noopener,noreferrer');
@@ -56,8 +56,10 @@ export const SETTINGS_CSS = `
56
56
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-row{
57
57
  display:flex;align-items:center;justify-content:space-between;gap:12px;
58
58
  padding:8px 14px;min-height:30px;}
59
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-labelcol{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px;}
59
60
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-label{
60
- display:inline-flex;align-items:center;gap:6px;color:rgba(255,255,255,.62);font-size:13px;font-weight:500;}
61
+ display:inline-flex;align-items:center;gap:6px;color:rgba(255,255,255,.86);font-size:13px;font-weight:500;}
62
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-desc{color:rgba(255,255,255,.5);font-size:11px;line-height:1.35;}
61
63
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-help{
62
64
  display:inline-flex;align-items:center;justify-content:center;
63
65
  width:14px;height:14px;border:none;border-radius:999px;padding:0;cursor:help;
@@ -86,7 +88,7 @@ export const SETTINGS_CSS = `
86
88
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-toggle:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:2px;}
87
89
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-row[data-disabled="1"]{opacity:.45;pointer-events:none;}
88
90
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-section{
89
- padding:10px 14px 6px;color:rgba(255,255,255,.38);font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;}
91
+ padding:10px 14px 6px;color:rgba(255,255,255,.38);font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;}
90
92
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatches{
91
93
  display:flex;gap:8px;flex-wrap:wrap;padding:0 14px 10px;}
92
94
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatch{
@@ -98,13 +100,13 @@ export const SETTINGS_CSS = `
98
100
  border-color:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.35);}
99
101
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatch:focus-visible{outline:2px solid rgba(59,130,246,.65);outline-offset:2px;}
100
102
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-checkrow{
101
- display:flex;align-items:center;gap:10px;padding:7px 14px;color:rgba(255,255,255,.8);font-size:13px;
103
+ display:flex;align-items:center;gap:10px;padding:7px 14px;color:rgba(255,255,255,.86);font-size:13px;
102
104
  cursor:pointer;user-select:none;margin:0;transition:background .12s;}
103
105
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-checkrow:hover{background:rgba(255,255,255,.03);}
104
106
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-check{
105
107
  width:16px;height:16px;border-radius:5px;border:1px solid rgba(255,255,255,.22);
106
108
  background:rgba(255,255,255,.04);display:inline-flex;align-items:center;justify-content:center;
107
- color:transparent;font-size:10px;line-height:1;flex:none;transition:background .12s,border-color .12s,color .12s;}
109
+ color:transparent;font-size:11px;line-height:1;flex:none;transition:background .12s,border-color .12s,color .12s;}
108
110
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-check[data-on="1"]{
109
111
  background:#fafafa;border-color:#fafafa;color:#111;}
110
112
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-check:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:2px;}
@@ -125,6 +127,18 @@ export const SETTINGS_CSS = `
125
127
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link-caret{display:inline-flex;align-items:center;flex:none;opacity:.55;}
126
128
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link:hover{background:rgba(255,255,255,.04);color:#fff;}
127
129
  [${SETTINGS_PANEL_ATTR}] .reticle-settings-link:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:-2px;}
130
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-kill-row{padding:12px 14px 14px;border-top:1px solid rgba(255,255,255,.08);}
131
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-kill{
132
+ width:100%;display:flex;align-items:center;justify-content:center;
133
+ border:1px solid rgba(239,68,68,.55);border-radius:8px;background:rgba(239,68,68,.12);color:#f87171;
134
+ font:inherit;font-size:13px;font-weight:600;padding:8px 12px;cursor:pointer;
135
+ transition:background .12s,border-color .12s,color .12s;}
136
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-kill:hover{background:rgba(239,68,68,.22);border-color:#ef4444;color:#fca5a5;}
137
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-kill[data-armed="1"]{background:#ef4444;border-color:#ef4444;color:#fff;}
138
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-kill:focus-visible{outline:2px solid rgba(239,68,68,.6);outline-offset:2px;}
139
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-kill-sub{margin:8px 0 0;font-size:11px;line-height:1.45;color:rgba(255,255,255,.55);}
140
+ /* The SDK version line sits straight in the foot, so it takes the rows' inset rather than none. */
141
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-foot > .reticle-settings-kill-sub{padding:0 14px;}
128
142
  [${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${'data-reticle-hud'}] .reticle-tb-tip{
129
143
  opacity:0;visibility:hidden;transition-delay:0s;}
130
144
  /* The dock's accent IS the theme's active colour, published as --reticle-accent on the overlay by