@reticlehq/browser 3.6.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 (83) 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 +12 -0
  5. package/dist/actions/actions-press.d.ts +1 -0
  6. package/dist/actions/actions-press.js +25 -1
  7. package/dist/actions/actions.d.ts +1 -37
  8. package/dist/actions/actions.js +24 -66
  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 +28 -2
  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/dom/a11y.js +251 -10
  16. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  17. package/dist/dom/addressing/coverage-markers.js +13 -0
  18. package/dist/dom/dom-ignore.d.ts +2 -0
  19. package/dist/dom/dom-ignore.js +15 -2
  20. package/dist/dom/query.d.ts +1 -1
  21. package/dist/dom/query.js +78 -16
  22. package/dist/dom/render-page.d.ts +12 -0
  23. package/dist/dom/render-page.js +135 -0
  24. package/dist/dom/snapshot.js +55 -7
  25. package/dist/observers/dom.js +32 -16
  26. package/dist/observers/field.js +3 -21
  27. package/dist/observers/network.js +56 -7
  28. package/dist/observers/perf.js +53 -1
  29. package/dist/observers/route.js +6 -5
  30. package/dist/presenter/carousel/carousel.js +2 -7
  31. package/dist/presenter/carousel/panel-slides.d.ts +2 -4
  32. package/dist/presenter/carousel/panel-slides.js +17 -15
  33. package/dist/presenter/chrome/log-memory.d.ts +1 -0
  34. package/dist/presenter/chrome/presenter-log.d.ts +7 -0
  35. package/dist/presenter/chrome/presenter-log.js +48 -8
  36. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  37. package/dist/presenter/chrome/presenter-morph.js +34 -0
  38. package/dist/presenter/chrome/presenter-report-copy.d.ts +17 -4
  39. package/dist/presenter/chrome/presenter-report-copy.js +26 -4
  40. package/dist/presenter/hud-telemetry.js +55 -4
  41. package/dist/presenter/presenter-account-styles.js +3 -3
  42. package/dist/presenter/presenter-account.d.ts +27 -5
  43. package/dist/presenter/presenter-account.js +83 -6
  44. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  45. package/dist/presenter/presenter-agent-link.js +62 -0
  46. package/dist/presenter/presenter-chat-views.d.ts +27 -5
  47. package/dist/presenter/presenter-chat-views.js +312 -72
  48. package/dist/presenter/presenter-config.d.ts +2 -0
  49. package/dist/presenter/presenter-config.js +1 -0
  50. package/dist/presenter/presenter-controls.d.ts +0 -1
  51. package/dist/presenter/presenter-controls.js +13 -91
  52. package/dist/presenter/presenter-design-tokens.js +1 -1
  53. package/dist/presenter/presenter-frame.d.ts +1 -1
  54. package/dist/presenter/presenter-frame.js +8 -10
  55. package/dist/presenter/presenter-harness-row.d.ts +67 -0
  56. package/dist/presenter/presenter-harness-row.js +209 -0
  57. package/dist/presenter/presenter-personas.d.ts +35 -0
  58. package/dist/presenter/presenter-personas.js +95 -0
  59. package/dist/presenter/presenter-plan.js +25 -24
  60. package/dist/presenter/presenter-report-styles.js +40 -22
  61. package/dist/presenter/presenter-report.d.ts +2 -1
  62. package/dist/presenter/presenter-report.js +83 -17
  63. package/dist/presenter/presenter-settings-styles.js +8 -4
  64. package/dist/presenter/presenter-settings.d.ts +7 -6
  65. package/dist/presenter/presenter-settings.js +75 -34
  66. package/dist/presenter/presenter-shell-styles.js +12 -3
  67. package/dist/presenter/presenter-shell.d.ts +10 -4
  68. package/dist/presenter/presenter-shell.js +31 -23
  69. package/dist/presenter/presenter-styles.js +0 -2
  70. package/dist/presenter/presenter-workspace.js +10 -3
  71. package/dist/presenter/presenter.d.ts +3 -2
  72. package/dist/presenter/presenter.js +81 -20
  73. package/dist/registry/declared-platform.js +2 -0
  74. package/dist/reticle-inject.js +777 -700
  75. package/dist/reticle.js +36 -2
  76. package/dist/storage-keys.d.ts +3 -2
  77. package/dist/storage-keys.js +7 -3
  78. package/dist/transport/transport.js +5 -3
  79. package/dist/transport/unreachable-message.d.ts +1 -1
  80. package/dist/transport/unreachable-message.js +3 -5
  81. package/package.json +2 -2
  82. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  83. package/dist/presenter/carousel/offer-card.js +0 -56
@@ -1,23 +1,29 @@
1
- import { offerHtml } from './offer-card.js';
2
1
  import { HudNoticeSchema } from '@reticlehq/core/hud';
3
2
  import { esc } from '../chrome/presenter-safe-html.js';
4
3
  import { DEFAULT_PLATFORM_URL } from '@reticlehq/core';
5
4
  const HARNESS_PATH = '/settings?group=billing';
6
5
  export const SLIDE_ID = {
7
- HARNESS: 'harness-journeys',
8
6
  REPLAY: 'replay-confidence',
9
- OFFER: 'harness-offer',
7
+ NOTES: 'notes-for-agent',
8
+ };
9
+ const SLIDE_TEXT = {
10
+ REPLAY_TITLE: 'Replay what your agent verified',
11
+ REPLAY_DETAIL: 'Saved flows re-run after each edit.',
12
+ REPLAY_LINK: 'See how →',
13
+ NOTES_TITLE: 'Point your agent at anything',
14
+ NOTES_DETAIL: 'Open Notes, then click an element.',
10
15
  };
11
16
  const valueSlides = (base) => [
12
17
  {
13
- id: SLIDE_ID.HARNESS,
14
- html: `<div class="reticle-promo-copy"><span class="reticle-promo-kicker">RETICLE HARNESS</span><strong class="reticle-promo-title">Let Reticle drive your app for you</strong><span class="reticle-promo-detail">Describe a user; it drives the journey and saves it to replay.</span><a data-reticle-promo-link class="reticle-promo-link" href="${base}${HARNESS_PATH}" target="_blank" rel="noopener noreferrer">Explore →</a></div>`,
18
+ id: SLIDE_ID.REPLAY,
19
+ html: `<div class="reticle-promo-copy"><strong class="reticle-promo-title">${SLIDE_TEXT.REPLAY_TITLE}</strong><span class="reticle-promo-detail">${SLIDE_TEXT.REPLAY_DETAIL}</span><a data-reticle-promo-link class="reticle-promo-link" href="${base}${HARNESS_PATH}" target="_blank" rel="noopener noreferrer">${SLIDE_TEXT.REPLAY_LINK}</a></div>`,
15
20
  },
16
21
  {
17
- id: SLIDE_ID.REPLAY,
18
- html: `<div class="reticle-promo-copy"><span class="reticle-promo-kicker">FROM RUN TO REGRESSION</span><strong class="reticle-promo-title">Keep every journey replayable</strong><span class="reticle-promo-detail">Catch what changes before users do.</span><a data-reticle-promo-link class="reticle-promo-link" href="${base}${HARNESS_PATH}" target="_blank" rel="noopener noreferrer">See how →</a></div>`,
22
+ id: SLIDE_ID.NOTES,
23
+ html: `<div class="reticle-promo-copy"><strong class="reticle-promo-title">${SLIDE_TEXT.NOTES_TITLE}</strong><span class="reticle-promo-detail">${SLIDE_TEXT.NOTES_DETAIL}</span></div>`,
19
24
  },
20
25
  ];
26
+ const HARNESS_NOTICE_PREFIX = 'harness';
21
27
  const NOTICE_SLIDE_PREFIX = 'notice-';
22
28
  function noticeSlide(notice) {
23
29
  const kicker = notice.kicker === undefined
@@ -25,7 +31,7 @@ function noticeSlide(notice) {
25
31
  : `<span class="reticle-promo-kicker">${esc(notice.kicker)}</span>`;
26
32
  const detail = notice.detail === undefined
27
33
  ? ''
28
- : `<span class="reticle-promo-detail">${esc(notice.detail)}</span>`;
34
+ : `<span class="reticle-promo-detail" title="${esc(notice.detail)}">${esc(notice.detail)}</span>`;
29
35
  const link = notice.cta === undefined
30
36
  ? ''
31
37
  : `<a data-reticle-promo-link class="reticle-promo-link" href="${esc(notice.cta.url)}" target="_blank" rel="noopener noreferrer">${esc(notice.cta.label)} →</a>`;
@@ -34,14 +40,10 @@ function noticeSlide(notice) {
34
40
  html: `<div class="reticle-promo-copy">${kicker}<strong class="reticle-promo-title">${esc(notice.title)}</strong>${detail}${link}</div>`,
35
41
  };
36
42
  }
37
- export function panelSlides(offer, offerDeclined, notices = [], base = DEFAULT_PLATFORM_URL) {
38
- const harnessOffer = offerHtml(offer, offerDeclined);
43
+ export function panelSlides(notices = [], base = DEFAULT_PLATFORM_URL) {
39
44
  const valid = notices.flatMap((n) => {
40
45
  const parsed = HudNoticeSchema.safeParse(n);
41
- return parsed.success ? [parsed.data] : [];
46
+ return parsed.success && !parsed.data.id.startsWith(HARNESS_NOTICE_PREFIX) ? [parsed.data] : [];
42
47
  });
43
- return [
44
- ...('' === harnessOffer ? [] : [{ id: SLIDE_ID.OFFER, html: harnessOffer }]),
45
- ...(0 < valid.length ? valid.map(noticeSlide) : valueSlides(base)),
46
- ];
48
+ return 0 < valid.length ? valid.map(noticeSlide) : [...valueSlides(base)];
47
49
  }
@@ -5,6 +5,7 @@ export interface RememberedRow {
5
5
  ts: string;
6
6
  at: number;
7
7
  actor?: LogActor;
8
+ summary?: true;
8
9
  result?: LogResult;
9
10
  }
10
11
  export declare function readRememberedLog(): RememberedRow[];
@@ -3,8 +3,15 @@ import { LOG_ACTOR, LOG_KIND, LOG_RESULT, type LogActor, type LogKind, type LogR
3
3
  export { LOG_ACTOR, LOG_KIND, LOG_RESULT };
4
4
  export type { LogActor, LogKind, LogResult };
5
5
  export declare const CHIP_LABEL: Record<PresenterMode, string>;
6
+ export declare const LAST_ACTOR_ATTR = "data-reticle-log-actor";
6
7
  export declare const DATA_RETICLE_LOG = "data-reticle-log";
7
8
  export declare const LOG_TIME_ATTR = "data-reticle-log-time";
9
+ export declare const LOG_EMPTY_TEXT: {
10
+ readonly TITLE: "Reticle checks your app from the inside.";
11
+ readonly OFFLINE_TITLE: "Not connected yet.";
12
+ readonly OFFLINE_BODY: "Once Reticle is running, this page connects by itself and the steps show here.";
13
+ readonly BODY: "Ask your coding agent to verify a change with Reticle: each step shows here, live.\\AOr open Harness below: Reticle clicks through the app as the person you pick and reports what breaks.";
14
+ };
8
15
  export declare const LOG_CSS: string;
9
16
  export declare const LOG_SCROLLABLE_ATTR = "data-reticle-log-scrollable";
10
17
  export declare function settleLogAtLatest(container: HTMLElement): void;
@@ -25,7 +25,7 @@ const HANDOVER_TEXT = {
25
25
  agent: 'Your agent is driving',
26
26
  harness: 'Reticle Harness is driving',
27
27
  };
28
- const LAST_ACTOR_ATTR = 'data-reticle-log-actor';
28
+ export const LAST_ACTOR_ATTR = 'data-reticle-log-actor';
29
29
  const DATA_ACTOR = 'data-actor';
30
30
  const DATA_STATE = 'data-state';
31
31
  const HANDOVER_KIND = 'handover';
@@ -35,18 +35,29 @@ const DATA_RETICLE_LOG_ROW = 'data-reticle-log-row';
35
35
  export const LOG_TIME_ATTR = 'data-reticle-log-time';
36
36
  const DATA_KIND = 'data-kind';
37
37
  const LOG_TEXT_CLASS = 'reticle-log-text';
38
+ const REPEAT_ATTR = 'data-reticle-repeat';
39
+ const LOG_REPEAT_CLASS = 'reticle-log-repeat';
38
40
  const LOG_RES_CLASS = 'reticle-res';
39
41
  const LOG_CHIP_CLASS = 'reticle-chip';
40
- const LOG_EMPTY_HINT = 'Agent activity will appear here';
42
+ export const LOG_EMPTY_TEXT = {
43
+ TITLE: 'Reticle checks your app from the inside.',
44
+ OFFLINE_TITLE: 'Not connected yet.',
45
+ OFFLINE_BODY: 'Once Reticle is running, this page connects by itself and the steps show here.',
46
+ BODY: 'Ask your coding agent to verify a change with Reticle: each step shows here, live.\\AOr open Harness below: Reticle clicks through the app as the person you pick and reports what breaks.',
47
+ };
41
48
  const LOG_SETTLE_MS = 160;
42
49
  export const LOG_CSS = `
43
50
  [data-reticle-log]{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;pointer-events:auto;touch-action:pan-y;
44
51
  display:flex;flex-direction:column;
45
52
  gap:4px;padding:8px 10px 10px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;
46
53
  background:transparent;}
47
- [data-reticle-log]:empty{align-items:center;justify-content:center;}
48
- [data-reticle-log]:empty::after{content:"${LOG_EMPTY_HINT}";display:block;padding:24px 16px;text-align:center;
49
- color:var(--reticle-faint);font-size:11.5px;line-height:1.5;letter-spacing:.01em;}
54
+ [data-reticle-log]:empty{align-items:center;justify-content:center;gap:6px;padding:16px 18px;}
55
+ [data-reticle-log]:empty::before{content:"${LOG_EMPTY_TEXT.TITLE}";display:block;text-align:center;
56
+ color:var(--reticle-hud-text);font-size:var(--reticle-hud-size-base);font-weight:600;line-height:1.4;}
57
+ [data-reticle-log]:empty::after{content:"${LOG_EMPTY_TEXT.BODY}";display:block;max-width:260px;text-align:center;white-space:pre-line;
58
+ color:var(--reticle-hud-text-muted);font-size:var(--reticle-hud-size-sm);line-height:1.5;}
59
+ [data-reticle-state="unreachable"] [data-reticle-log]:empty::before{content:"${LOG_EMPTY_TEXT.OFFLINE_TITLE}";}
60
+ [data-reticle-state="unreachable"] [data-reticle-log]:empty::after{content:"${LOG_EMPTY_TEXT.OFFLINE_BODY}";}
50
61
  [data-reticle-log]::-webkit-scrollbar{width:8px;}
51
62
  [data-reticle-log]::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:8px;border:2px solid transparent;background-clip:content-box;}
52
63
  [data-reticle-log]::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.26);background-clip:content-box;}
@@ -69,8 +80,9 @@ export const LOG_CSS = `
69
80
  [data-reticle-log] [data-reticle-log-row] .reticle-chip-label{
70
81
  position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}
71
82
  [data-reticle-log] [data-reticle-log-row][data-kind="read"] .reticle-chip{opacity:.5;}
72
- [${LOG_TIME_ATTR}]{flex:none;color:var(--reticle-faint);font-size:9px;font-variant-numeric:tabular-nums;padding-top:2px;min-width:2em;opacity:.85;}
83
+ [${LOG_TIME_ATTR}]{flex:none;color:var(--reticle-faint);font-size:11px;font-variant-numeric:tabular-nums;padding-top:2px;min-width:2em;opacity:.85;}
73
84
  [data-reticle-log] .reticle-log-text{flex:1;min-width:0;color:var(--reticle-muted);overflow-wrap:anywhere;word-break:break-word;}
85
+ [data-reticle-log] .reticle-log-repeat{flex:none;color:var(--reticle-muted);font-variant-numeric:tabular-nums;opacity:.7;}
74
86
  [data-reticle-log] .reticle-res{flex:none;font-size:7.5px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--reticle-bad);opacity:.75;padding-top:2px;}
75
87
  [data-reticle-log] .reticle-res.reticle-pass{display:none;}
76
88
  [data-reticle-log-row][data-kind="human"]{align-self:flex-end;max-width:78%;margin:6px 0 2px;
@@ -85,14 +97,14 @@ export const LOG_CSS = `
85
97
  [data-reticle-log-row][data-state="running"] .reticle-res{display:inline-block;width:6px;height:6px;margin-top:5px;
86
98
  border-radius:50%;background:var(--reticle-c-active);opacity:1;animation:reticle-row-pulse 1s ease-in-out infinite;}
87
99
  @keyframes reticle-row-pulse{0%,100%{opacity:.35;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}
88
- [data-reticle-log] .reticle-res.reticle-pass{display:inline;color:#4ade80;font-size:10px;opacity:.9;}
100
+ [data-reticle-log] .reticle-res.reticle-pass{display:inline;color:#4ade80;font-size:11px;opacity:.9;}
89
101
  [data-reticle-log] .reticle-res.reticle-fail{color:#fca5a5;background:rgba(239,68,68,.16);border-radius:999px;padding:1px 6px;font-size:8px;opacity:1;}
90
102
  /* The Harness's rows wear its own colour, so two drivers never read as one. */
91
103
  [data-reticle-log-row][data-actor="harness"]{--reticle-row-accent:#a78bfa;}
92
104
  [data-reticle-log-row][data-actor="harness"] .reticle-chip{color:#a78bfa;}
93
105
  [data-reticle-log-row][data-actor="harness"]:not([data-kind="handover"]){box-shadow:inset 2px 0 0 color-mix(in srgb,#a78bfa 55%,transparent);padding-left:8px;}
94
106
  [data-reticle-log-row][data-kind="handover"]{align-items:center;gap:8px;margin:8px 0 4px;padding:0;
95
- font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--reticle-row-accent,var(--reticle-c-active));}
107
+ font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--reticle-row-accent,var(--reticle-c-active));}
96
108
  [data-reticle-log-row][data-kind="handover"]::before,[data-reticle-log-row][data-kind="handover"]::after{
97
109
  content:"";flex:1;height:1px;background:color-mix(in srgb,var(--reticle-row-accent,var(--reticle-c-active)) 40%,transparent);}
98
110
  [data-reticle-log-row][data-kind="handover"] .reticle-log-text{flex:none;color:inherit;}
@@ -144,6 +156,23 @@ export function formatElapsed(ms) {
144
156
  return humanDuration(ms);
145
157
  }
146
158
  export function appendLogRow(container, kind, text, ts, logMax, actor = LOG_ACTOR.AGENT) {
159
+ const repeated = kind === LOG_KIND.READ ? sameReadAsLast(container, text, actor) : undefined;
160
+ if (repeated !== undefined) {
161
+ const times = Number(repeated.getAttribute(REPEAT_ATTR) ?? '1') + 1;
162
+ repeated.setAttribute(REPEAT_ATTR, String(times));
163
+ let count = repeated.querySelector(`.${LOG_REPEAT_CLASS}`);
164
+ if (null === count) {
165
+ count = document.createElement('span');
166
+ count.className = LOG_REPEAT_CLASS;
167
+ repeated.querySelector(`.${LOG_TEXT_CLASS}`)?.after(count);
168
+ }
169
+ count.textContent = `×${String(times)}`;
170
+ const time = repeated.querySelector(`[${LOG_TIME_ATTR}]`);
171
+ if (null !== time)
172
+ time.textContent = ts;
173
+ const res = repeated.querySelector(`.${LOG_RES_CLASS}`);
174
+ return null === res ? { result: () => undefined } : rowHandle(repeated, res);
175
+ }
147
176
  const driven = kind === LOG_KIND.ACT || kind === LOG_KIND.READ;
148
177
  if (driven && (container.getAttribute(LAST_ACTOR_ATTR) ?? LOG_ACTOR.AGENT) !== actor) {
149
178
  const divider = document.createElement('div');
@@ -200,6 +229,17 @@ export function appendLogRow(container, kind, text, ts, logMax, actor = LOG_ACTO
200
229
  requestAnimationFrame(() => {
201
230
  scrollLogToLatest(container);
202
231
  });
232
+ return rowHandle(row, resEl);
233
+ }
234
+ function sameReadAsLast(container, text, actor) {
235
+ const last = container.lastElementChild;
236
+ if (!(last instanceof HTMLElement) || !last.hasAttribute(DATA_RETICLE_LOG_ROW))
237
+ return undefined;
238
+ if (last.getAttribute(DATA_KIND) !== LOG_KIND.READ || last.getAttribute(DATA_ACTOR) !== actor)
239
+ return undefined;
240
+ return last.querySelector(`.${LOG_TEXT_CLASS}`)?.textContent === text ? last : undefined;
241
+ }
242
+ function rowHandle(row, resEl) {
203
243
  return {
204
244
  result: (r) => {
205
245
  row.setAttribute(DATA_STATE, r);
@@ -0,0 +1 @@
1
+ export declare function morphHtml(target: Element, html: string): void;
@@ -0,0 +1,34 @@
1
+ export function morphHtml(target, html) {
2
+ const next = document.createElement('template');
3
+ next.innerHTML = html;
4
+ morphChildren(target, next.content);
5
+ }
6
+ function morphChildren(from, to) {
7
+ const have = [...from.childNodes];
8
+ const want = [...to.childNodes];
9
+ want.forEach((node, i) => {
10
+ const old = have[i];
11
+ if (old === undefined)
12
+ from.appendChild(node);
13
+ else if (old.nodeType === node.nodeType && old.nodeName === node.nodeName)
14
+ morphNode(old, node);
15
+ else
16
+ from.replaceChild(node, old);
17
+ });
18
+ for (const extra of have.slice(want.length))
19
+ from.removeChild(extra);
20
+ }
21
+ function morphNode(old, node) {
22
+ if (!(old instanceof Element) || !(node instanceof Element)) {
23
+ if (old.nodeValue !== node.nodeValue)
24
+ old.nodeValue = node.nodeValue;
25
+ return;
26
+ }
27
+ for (const { name } of [...old.attributes])
28
+ if (!node.hasAttribute(name))
29
+ old.removeAttribute(name);
30
+ for (const { name, value } of [...node.attributes])
31
+ if (old.getAttribute(name) !== value)
32
+ old.setAttribute(name, value);
33
+ morphChildren(old, node);
34
+ }
@@ -13,7 +13,7 @@ export declare const REPORT_TEXT: {
13
13
  readonly GLOBAL: "All projects";
14
14
  readonly GLOBAL_TITLE: "Every project on this computer";
15
15
  readonly SCOPE_LABEL: "Show impact for";
16
- readonly HERO_DEFECTS: "checks Reticle refused to pass";
16
+ readonly heroDefects: (n: number) => string;
17
17
  readonly VERDICTS: "Verdicts";
18
18
  readonly PASSED: "passed";
19
19
  readonly FAILED: "failed";
@@ -35,18 +35,31 @@ export declare const REPORT_TEXT: {
35
35
  readonly DEFECTS_SEE_ALL_TITLE: "Sign up free, link this project, and see every bug Reticle caught";
36
36
  readonly DEFECT_LINK_TITLE: "Open on the dashboard to triage or push to GitHub";
37
37
  readonly LOCAL_ONLY: "This record stops at this machine.";
38
- readonly LOCAL_ONLY_ACTION: "reticle login";
38
+ readonly LOCAL_ONLY_ACTION: "reticle connect";
39
39
  readonly LOCAL_ONLY_TAIL: "keeps it, and lets a team see it.";
40
40
  readonly UNLINKED: "Signed in, but this repo is not linked.";
41
- readonly UNLINKED_ACTION: "reticle link";
41
+ readonly UNLINKED_ACTION: "reticle connect";
42
42
  readonly UNLINKED_TAIL: "sends this record to your workspace.";
43
43
  readonly DEFECTS_NONE: "Nothing has failed a declared consequence yet.";
44
44
  readonly EMPTY: "Nothing recorded yet. Drive the app once and this fills in.";
45
45
  readonly SHARE: "Share";
46
46
  readonly COPY: "Copy";
47
47
  readonly COPIED: "Copied";
48
- readonly REFER: "Send to a friend";
48
+ readonly SEE_RUN: "See it in your dashboard";
49
+ readonly KEY_REFUSED: "Key refused";
50
+ readonly NOT_SENT: "not synced";
51
+ readonly NOT_SENT_HELP: "Runs saved on this machine but not on your dashboard yet. Run reticle connect in this project to send them.";
52
+ readonly INSTRUMENTATION: "What Reticle can see";
53
+ readonly INSTRUMENTATION_HELP: "What Reticle can see in this app, from its handshake and the gaps its verdicts recorded";
54
+ readonly covered: (covered: number, total: number) => string;
55
+ readonly NOT_SEEN_YET: "not checked yet";
56
+ readonly COPY_PROMPT: "Copy prompt for your coding agent";
57
+ readonly SHARE_LABEL: "Share";
58
+ readonly POST_X: "X";
59
+ readonly POST_IN: "LinkedIn";
60
+ readonly REFER: "Invite a friend";
49
61
  };
62
+ export declare const CAPABILITY_WORDS: Readonly<Record<string, string>>;
50
63
  export declare function compactNumber(n: number): string;
51
64
  export declare function compactDuration(ms: number): string;
52
65
  export declare function buildShareText(scope: ImpactScope, projectName?: string): string;
@@ -14,7 +14,7 @@ export const REPORT_TEXT = {
14
14
  GLOBAL: 'All projects',
15
15
  GLOBAL_TITLE: 'Every project on this computer',
16
16
  SCOPE_LABEL: 'Show impact for',
17
- HERO_DEFECTS: 'checks Reticle refused to pass',
17
+ heroDefects: (n) => `failed check${1 === n ? '' : 's'} caught before you shipped`,
18
18
  VERDICTS: 'Verdicts',
19
19
  PASSED: 'passed',
20
20
  FAILED: 'failed',
@@ -36,17 +36,39 @@ export const REPORT_TEXT = {
36
36
  DEFECTS_SEE_ALL_TITLE: 'Sign up free, link this project, and see every bug Reticle caught',
37
37
  DEFECT_LINK_TITLE: 'Open on the dashboard to triage or push to GitHub',
38
38
  LOCAL_ONLY: 'This record stops at this machine.',
39
- LOCAL_ONLY_ACTION: 'reticle login',
39
+ LOCAL_ONLY_ACTION: 'reticle connect',
40
40
  LOCAL_ONLY_TAIL: 'keeps it, and lets a team see it.',
41
41
  UNLINKED: 'Signed in, but this repo is not linked.',
42
- UNLINKED_ACTION: 'reticle link',
42
+ UNLINKED_ACTION: 'reticle connect',
43
43
  UNLINKED_TAIL: 'sends this record to your workspace.',
44
44
  DEFECTS_NONE: 'Nothing has failed a declared consequence yet.',
45
45
  EMPTY: 'Nothing recorded yet. Drive the app once and this fills in.',
46
46
  SHARE: 'Share',
47
47
  COPY: 'Copy',
48
48
  COPIED: 'Copied',
49
- REFER: 'Send to a friend',
49
+ SEE_RUN: 'See it in your dashboard',
50
+ KEY_REFUSED: 'Key refused',
51
+ NOT_SENT: 'not synced',
52
+ NOT_SENT_HELP: 'Runs saved on this machine but not on your dashboard yet. Run reticle connect in this project to send them.',
53
+ INSTRUMENTATION: 'What Reticle can see',
54
+ INSTRUMENTATION_HELP: 'What Reticle can see in this app, from its handshake and the gaps its verdicts recorded',
55
+ covered: (covered, total) => `${String(covered)} of ${String(total)}`,
56
+ NOT_SEEN_YET: 'not checked yet',
57
+ COPY_PROMPT: 'Copy prompt for your coding agent',
58
+ SHARE_LABEL: 'Share',
59
+ POST_X: 'X',
60
+ POST_IN: 'LinkedIn',
61
+ REFER: 'Invite a friend',
62
+ };
63
+ export const CAPABILITY_WORDS = {
64
+ 'page connected': 'Page connected',
65
+ 'DOM actions': 'Clicks and typing',
66
+ network: 'Network requests',
67
+ console: 'Console errors',
68
+ 'app state (store registered)': 'App state',
69
+ 'signals on mutation': 'Success signals from your code',
70
+ 'file:line source mapping': 'Source file and line',
71
+ 'stable test ids': 'Stable test ids',
50
72
  };
51
73
  export function compactNumber(n) {
52
74
  if (n < 1000)
@@ -1,9 +1,12 @@
1
1
  import { HUD_KEYED_ATTRS, isHudControl } from '@reticlehq/core/hud';
2
- import { HudPanel, HudToggle, HudView } from '@reticlehq/core';
2
+ import { HudPanel, HudPromptSurface, HudToggle, HudView } from '@reticlehq/core';
3
3
  import { isReticleUi } from '../dom/dom-ignore.js';
4
4
  import { nativeSetTimeout } from '../timers/native/native-timers.js';
5
5
  import { CHAT_ATTR, MIN_ATTR, REPORT_ATTR, SETTINGS_ATTR } from './presenter-config.js';
6
6
  import { CAROUSEL_SLIDE_ATTR } from './carousel/carousel.js';
7
+ import { ACCOUNT_MENU_ATTR, ACCOUNT_SIGNIN_ATTR, TEAM_SAVE_ATTR } from './presenter-account.js';
8
+ import { HARNESS_LINK_ATTR } from './presenter-harness-row.js';
9
+ import { RAIL_SIGNIN_ATTR } from './presenter-frame.js';
7
10
  const PREFIX = 'data-reticle-';
8
11
  const PAGE_OPEN_ATTR = 'data-reticle-page-open';
9
12
  const TOGGLE_ROLES = new Set(['switch', 'checkbox']);
@@ -30,6 +33,14 @@ function controlOf(path) {
30
33
  return undefined;
31
34
  }
32
35
  function viewOf(root) {
36
+ const page = pageOf(root);
37
+ if (HudView.BUBBLE === page.view)
38
+ return page;
39
+ return null === root.querySelector(`[${ACCOUNT_MENU_ATTR}]:not([hidden])`)
40
+ ? page
41
+ : { view: page.view, panel: HudPanel.ACCOUNT };
42
+ }
43
+ function pageOf(root) {
33
44
  if ('1' === root.getAttribute(MIN_ATTR))
34
45
  return { view: HudView.BUBBLE, panel: HudPanel.NONE };
35
46
  if ('1' === root.getAttribute(SETTINGS_ATTR)) {
@@ -43,10 +54,30 @@ function viewOf(root) {
43
54
  return { view: HudView.EXPANDED, panel: HudPanel.FLOWS };
44
55
  if ('annotations' === page)
45
56
  return { view: HudView.EXPANDED, panel: HudPanel.NOTES };
57
+ if ('harness' === page)
58
+ return { view: HudView.EXPANDED, panel: HudPanel.HARNESS };
46
59
  if ('1' === root.getAttribute(CHAT_ATTR))
47
60
  return { view: HudView.EXPANDED, panel: HudPanel.CHAT };
48
61
  return { view: HudView.COLLAPSED, panel: HudPanel.NONE };
49
62
  }
63
+ const PROMPTS = [
64
+ {
65
+ surface: HudPromptSurface.RAIL,
66
+ selector: `[${RAIL_SIGNIN_ATTR}]`,
67
+ shown: (at) => HudView.EXPANDED === at.view,
68
+ },
69
+ {
70
+ surface: HudPromptSurface.HARNESS,
71
+ selector: `[data-reticle-page-panel="harness"] :is([${ACCOUNT_SIGNIN_ATTR}],[${HARNESS_LINK_ATTR}])`,
72
+ shown: (at) => HudPanel.HARNESS === at.panel,
73
+ },
74
+ {
75
+ surface: HudPromptSurface.HEADER,
76
+ selector: `[${TEAM_SAVE_ATTR}]`,
77
+ shown: (at) => HudView.BUBBLE !== at.view,
78
+ },
79
+ ];
80
+ const visible = (el) => null === el.closest('[hidden]');
50
81
  export function installHudTelemetry(doc, root, report) {
51
82
  const send = (use) => {
52
83
  try {
@@ -91,9 +122,27 @@ export function installHudTelemetry(doc, root, report) {
91
122
  lastSlide = slide;
92
123
  send({ slide });
93
124
  };
94
- const slides = new MutationObserver(onSlide);
95
- slides.observe(root, {
125
+ const prompted = new Set();
126
+ const onPrompt = () => {
127
+ const at = viewOf(root);
128
+ for (const prompt of PROMPTS) {
129
+ if (prompted.has(prompt.surface) || !prompt.shown(at))
130
+ continue;
131
+ const el = root.querySelector(prompt.selector);
132
+ if (null === el || !visible(el))
133
+ continue;
134
+ prompted.add(prompt.surface);
135
+ send({ prompt: prompt.surface });
136
+ }
137
+ };
138
+ const inside = new MutationObserver(() => {
139
+ onSlide();
140
+ onChange();
141
+ onPrompt();
142
+ });
143
+ inside.observe(root, {
96
144
  subtree: true,
145
+ childList: true,
97
146
  attributes: true,
98
147
  attributeFilter: ['data-active', 'hidden'],
99
148
  });
@@ -102,15 +151,17 @@ export function installHudTelemetry(doc, root, report) {
102
151
  onChange();
103
152
  if (0 < records.length)
104
153
  onSlide();
154
+ onPrompt();
105
155
  });
106
156
  observer.observe(root, {
107
157
  attributes: true,
108
158
  attributeFilter: [MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR, REPORT_ATTR, PAGE_OPEN_ATTR],
109
159
  });
110
160
  onChange();
161
+ onPrompt();
111
162
  return () => {
112
163
  doc.removeEventListener('click', onPress, true);
113
164
  observer.disconnect();
114
- slides.disconnect();
165
+ inside.disconnect();
115
166
  };
116
167
  }
@@ -36,7 +36,7 @@ export const ACCOUNT_CSS = `
36
36
  .reticle-account-trigger[aria-expanded="true"]{box-shadow:0 0 0 2px rgba(255,255,255,.28);}
37
37
 
38
38
  .reticle-account-signin{
39
- border:none;border-radius:6px;padding:2px 9px;cursor:pointer;font:inherit;font-size:10.5px;
39
+ border:none;border-radius:6px;padding:2px 9px;cursor:pointer;font:inherit;font-size:11px;
40
40
  background:rgba(255,255,255,.06);color:rgba(255,255,255,.75);transition:background .12s,color .12s;}
41
41
  .reticle-account-signin:hover{background:rgba(255,255,255,.12);color:#fff;}
42
42
 
@@ -64,7 +64,7 @@ export const ACCOUNT_CSS = `
64
64
  font-size:11.5px;font-weight:600;color:rgba(255,255,255,.95);
65
65
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
66
66
  .reticle-account-email{
67
- font-size:10.5px;color:rgba(255,255,255,.55);
67
+ font-size:11px;color:rgba(255,255,255,.55);
68
68
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
69
69
 
70
70
  .reticle-account-rows{
@@ -90,7 +90,7 @@ export const ACCOUNT_CSS = `
90
90
  /* The unlinked hint is a sentence, not a verb: it wraps rather than truncating, because a clipped
91
91
  instruction is worse than a tall menu. */
92
92
  .reticle-account-action--hint{
93
- color:rgba(255,255,255,.5);font-size:10.5px;line-height:1.35;white-space:normal;cursor:pointer;}
93
+ color:rgba(255,255,255,.5);font-size:11px;line-height:1.35;white-space:normal;cursor:pointer;}
94
94
 
95
95
  .reticle-account-dashboard{
96
96
  display:inline-flex;align-items:center;justify-content:center;
@@ -1,14 +1,23 @@
1
- import type { AccountState } from '@reticlehq/core';
1
+ import { type AccountState, type HarnessConfig } from '@reticlehq/core';
2
2
  export declare const ACCOUNT_ROOT_ATTR = "data-reticle-account";
3
3
  export declare const ACCOUNT_SIGNIN_ATTR = "data-reticle-account-signin";
4
4
  export declare const SYNC_BTN_ATTR = "data-reticle-sync-now";
5
5
  export declare const ACCOUNT_TRIGGER_ATTR = "data-reticle-account-trigger";
6
6
  export declare const ACCOUNT_MENU_ATTR = "data-reticle-account-menu";
7
7
  export declare const ACCOUNT_SIGNOUT_ATTR = "data-reticle-account-signout";
8
+ export declare const TEAM_OPEN_ATTR = "data-reticle-team-open";
9
+ export declare const TEAM_SAVE_ATTR = "data-reticle-team-save";
10
+ export declare const TEAM_SLOT_ATTR = "data-reticle-team";
11
+ export declare const TEAM_TEXT: {
12
+ readonly OPEN: "Open on dashboard ↗";
13
+ readonly OPEN_TITLE: "This project's runs, kept for your team on the Reticle dashboard";
14
+ readonly SAVE_LINK: "Save runs for your team";
15
+ readonly SAVE_TITLE: `Keep every run, flow and Harness result on a dashboard your team shares. On Reticle, ${string}.`;
16
+ };
8
17
  export declare const ACCOUNT_TEXT: {
9
18
  readonly SIGNED_OUT: "Sign in";
10
- readonly SIGNIN_TITLE: "Sign in through your browser. Also copies `reticle login`, for a terminal";
11
- readonly SIGNIN_COMMAND: "reticle login";
19
+ readonly SIGNIN_TITLE: "Sign in through your browser. Also copies `reticle connect`, for a terminal";
20
+ readonly SIGNIN_COMMAND: "reticle connect";
12
21
  readonly DASHBOARD_TITLE: "Open this project on the dashboard";
13
22
  readonly SYNC_TITLE: "Push to the dashboard now, instead of waiting for the next sync";
14
23
  readonly COPIED: "Copied";
@@ -17,8 +26,8 @@ export declare const ACCOUNT_TEXT: {
17
26
  readonly TRIGGER_TITLE: "Your Reticle account";
18
27
  readonly SIGNED_IN_AS: "Signed in";
19
28
  readonly DASHBOARD_ACTION: "Open dashboard";
20
- readonly LINK_HINT: "Not linked — run `reticle link` to keep a record on the dashboard";
21
- readonly LINK_COMMAND: "reticle link";
29
+ readonly LINK_HINT: "Not linked — run `reticle connect` to keep a record on the dashboard";
30
+ readonly LINK_COMMAND: "reticle connect";
22
31
  readonly SIGNOUT_ACTION: "Copy sign-out command";
23
32
  readonly SIGNOUT_TITLE: "Copy `reticle logout` to run in your terminal";
24
33
  readonly SIGNOUT_COMMAND: "reticle logout";
@@ -27,15 +36,28 @@ export declare const ACCOUNT_TEXT: {
27
36
  readonly HOST_LABEL: "Host";
28
37
  readonly VERDICTS_LABEL: "Verdicts";
29
38
  readonly DEFECTS_LABEL: "Defects found";
39
+ readonly PLAN_LABEL: "Plan";
40
+ readonly CREDITS_LABEL: "Credits";
41
+ readonly HARNESS_RUNS_LABEL: "Harness runs";
42
+ readonly MEMORY_LABEL: "Memory";
43
+ readonly ON_PLATFORM_LABEL: "Runs on dashboard";
30
44
  };
31
45
  export interface AccountDetails {
32
46
  projectName?: string | undefined;
33
47
  dashboardUrl?: string | undefined;
34
48
  verdicts?: number | undefined;
35
49
  defects?: number | undefined;
50
+ plan?: string | undefined;
51
+ credits?: string | undefined;
52
+ harnessRuns?: string | undefined;
53
+ memory?: string | undefined;
54
+ onPlatform?: string | undefined;
36
55
  }
37
56
  export declare function accountInitials(org: string | undefined): string;
38
57
  export declare function accountControlHtml(account: AccountState | undefined, details?: AccountDetails, offerSignIn?: boolean): string;
58
+ export declare function platformDetails(config: HarnessConfig | undefined, sync: Readonly<Record<string, unknown>> | undefined): Pick<AccountDetails, 'plan' | 'credits' | 'harnessRuns' | 'memory' | 'onPlatform'>;
59
+ export declare function teamLinkHtml(account: AccountState | undefined, dashboardUrl: string | undefined): string;
60
+ export declare function paintAccountSlot(slot: HTMLElement, html: string): void;
39
61
  export declare function accountCapsuleHtml(account: AccountState | undefined, dashboardUrl?: string, offerSignIn?: boolean): string;
40
62
  export declare function syncButtonHtml(dashboardUrl: string | undefined): string;
41
63
  export declare function mountAccountControl(root: HTMLElement, onSignIn?: () => void): () => void;