@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
@@ -1,8 +1,12 @@
1
+ import { ReticleStorageKey } from '../../storage-keys.js';
1
2
  export const CAROUSEL_ATTR = 'data-reticle-carousel';
2
3
  export const CAROUSEL_SLIDE_ATTR = 'data-reticle-carousel-slide';
3
4
  export const CAROUSEL_DOT_ATTR = 'data-reticle-carousel-dot';
4
5
  export const CAROUSEL_CLOSE_ATTR = 'data-reticle-carousel-close';
5
- export const CAROUSEL_DISMISSED_KEY = 'reticle.carousel.dismissed';
6
+ export const CAROUSEL_PREV_ATTR = 'data-reticle-carousel-prev';
7
+ export const CAROUSEL_NEXT_ATTR = 'data-reticle-carousel-next';
8
+ export const CAROUSEL_POSITION_ATTR = 'data-reticle-carousel-position';
9
+ export const CAROUSEL_DISMISSED_KEY = ReticleStorageKey.CAROUSEL_DISMISSED;
6
10
  const CLOSE_LABEL = 'Close';
7
11
  export const ROTATE_MS = 6000;
8
12
  const rotations = new WeakMap();
@@ -22,7 +26,11 @@ function prefersReducedMotion() {
22
26
  }
23
27
  function show(root, index) {
24
28
  root.setAttribute('data-active', String(index));
25
- root.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`).forEach((slide, i) => {
29
+ const slides = root.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`);
30
+ const position = root.querySelector(`[${CAROUSEL_POSITION_ATTR}]`);
31
+ if (position !== null)
32
+ position.textContent = `${String(index + 1)} / ${String(slides.length)}`;
33
+ slides.forEach((slide, i) => {
26
34
  slide.hidden = i !== index;
27
35
  });
28
36
  root.querySelectorAll(`[${CAROUSEL_DOT_ATTR}]`).forEach((dot, i) => {
@@ -37,15 +45,15 @@ export function carouselHtml(slides, active) {
37
45
  const track = slides
38
46
  .map((slide, i) => `<div ${CAROUSEL_SLIDE_ATTR}="${slide.id}" class="reticle-carousel-slide"${i === current ? '' : ' hidden'}>${slide.html}</div>`)
39
47
  .join('');
40
- const dots = 1 === slides.length
48
+ const controls = 1 === slides.length
41
49
  ? ''
42
- : `<div class="reticle-carousel-dots">${slides
43
- .map((slide, i) => `<button type="button" ${CAROUSEL_DOT_ATTR}="${String(i)}" class="reticle-carousel-dot" aria-label="${slide.id}" aria-current="${String(i === current)}"></button>`)
44
- .join('')}</div>`;
50
+ : `<div class="reticle-carousel-controls"><button type="button" ${CAROUSEL_PREV_ATTR} aria-label="Previous message" class="reticle-carousel-arrow">‹</button><div class="reticle-carousel-dots">${slides
51
+ .map((slide, i) => `<button type="button" ${CAROUSEL_DOT_ATTR}="${String(i)}" class="reticle-carousel-dot" aria-label="Show ${slide.id}" aria-current="${String(i === current)}"></button>`)
52
+ .join('')}</div><span ${CAROUSEL_POSITION_ATTR} aria-live="polite">${String(current + 1)} / ${String(slides.length)}</span><button type="button" ${CAROUSEL_NEXT_ATTR} aria-label="Next message" class="reticle-carousel-arrow">›</button></div>`;
45
53
  return `<div ${CAROUSEL_ATTR} class="reticle-carousel" role="region" aria-label="Reticle" data-active="${String(current)}">
46
54
  <button type="button" ${CAROUSEL_CLOSE_ATTR} class="reticle-carousel-close" aria-label="${CLOSE_LABEL}" title="${CLOSE_LABEL}">×</button>
47
55
  <div class="reticle-carousel-track">${track}</div>
48
- ${dots}
56
+ ${controls}
49
57
  </div>`;
50
58
  }
51
59
  function dismissed(storage) {
@@ -88,6 +96,16 @@ export function paintCarousel(log, slides, storage) {
88
96
  show(root, Number(dot.getAttribute(CAROUSEL_DOT_ATTR)));
89
97
  });
90
98
  }
99
+ root.querySelector(`[${CAROUSEL_PREV_ATTR}]`)?.addEventListener('click', (event) => {
100
+ event.preventDefault();
101
+ event.stopPropagation();
102
+ show(root, (Number(root.getAttribute('data-active')) + slides.length - 1) % slides.length);
103
+ });
104
+ root.querySelector(`[${CAROUSEL_NEXT_ATTR}]`)?.addEventListener('click', (event) => {
105
+ event.preventDefault();
106
+ event.stopPropagation();
107
+ show(root, (Number(root.getAttribute('data-active')) + 1) % slides.length);
108
+ });
91
109
  startRotation(log, root, slides.length);
92
110
  }
93
111
  function startRotation(log, root, count) {
@@ -117,10 +135,14 @@ function startRotation(log, root, count) {
117
135
  export const CAROUSEL_CSS = `
118
136
  .reticle-carousel{position:relative;margin:8px 10px;padding:10px 12px 8px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}
119
137
  .reticle-carousel-track{height:100px;overflow:hidden;}
120
- .reticle-carousel-slide .reticle-offer,.reticle-carousel-slide .reticle-talk{margin:0;padding:0;border:0;background:none;}
138
+ .reticle-carousel-slide .reticle-talk{margin:0;padding:0;border:0;background:none;}
121
139
  .reticle-carousel-close{position:absolute;top:4px;right:6px;width:20px;height:20px;padding:0;border:0;background:none;color:inherit;opacity:.55;cursor:pointer;font-size:14px;line-height:20px;}
122
140
  .reticle-carousel-close:hover{opacity:1;}
123
141
  .reticle-carousel-dots{display:flex;justify-content:center;gap:6px;margin-top:6px;}
124
142
  .reticle-carousel-dot{width:6px;height:6px;padding:0;border:0;border-radius:999px;background:currentColor;opacity:.3;cursor:pointer;}
125
143
  .reticle-carousel-dot[aria-current="true"]{opacity:.9;}
144
+ .reticle-carousel-controls{display:flex;align-items:center;gap:8px;margin-top:6px;}
145
+ .reticle-carousel-arrow{width:24px;height:24px;flex:none;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--reticle-line);border-radius:6px;background:rgba(255,255,255,.04);color:var(--reticle-fg);font:inherit;font-size:18px;line-height:1;cursor:pointer;}
146
+ .reticle-carousel-arrow:hover{border-color:var(--reticle-accent);background:var(--reticle-accent-soft);}
147
+ .reticle-carousel-position{margin-left:auto;color:var(--reticle-muted);font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap;}
126
148
  `;
@@ -1,7 +1,6 @@
1
- import { type OfferState } from './offer-card.js';
2
1
  import type { Slide } from './carousel.js';
3
2
  export declare const SLIDE_ID: {
4
- readonly HARNESS: "harness";
5
- readonly FOUNDERS: "founders";
3
+ readonly REPLAY: "replay-confidence";
4
+ readonly NOTES: "notes-for-agent";
6
5
  };
7
- export declare function panelSlides(offer: OfferState | undefined, offerDeclined: boolean): Slide[];
6
+ export declare function panelSlides(notices?: readonly unknown[], base?: string): Slide[];
@@ -1,10 +1,49 @@
1
- import { offerHtml } from './offer-card.js';
2
- import { talkCardHtml } from './talk-card.js';
3
- export const SLIDE_ID = { HARNESS: 'harness', FOUNDERS: 'founders' };
4
- export function panelSlides(offer, offerDeclined) {
5
- const harness = offerHtml(offer, offerDeclined);
6
- return [
7
- ...('' === harness ? [] : [{ id: SLIDE_ID.HARNESS, html: harness }]),
8
- { id: SLIDE_ID.FOUNDERS, html: talkCardHtml() },
9
- ];
1
+ import { HudNoticeSchema } from '@reticlehq/core/hud';
2
+ import { esc } from '../chrome/presenter-safe-html.js';
3
+ import { DEFAULT_PLATFORM_URL } from '@reticlehq/core';
4
+ const HARNESS_PATH = '/settings?group=billing';
5
+ export const SLIDE_ID = {
6
+ REPLAY: 'replay-confidence',
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.',
15
+ };
16
+ const valueSlides = (base) => [
17
+ {
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>`,
20
+ },
21
+ {
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>`,
24
+ },
25
+ ];
26
+ const HARNESS_NOTICE_PREFIX = 'harness';
27
+ const NOTICE_SLIDE_PREFIX = 'notice-';
28
+ function noticeSlide(notice) {
29
+ const kicker = notice.kicker === undefined
30
+ ? ''
31
+ : `<span class="reticle-promo-kicker">${esc(notice.kicker)}</span>`;
32
+ const detail = notice.detail === undefined
33
+ ? ''
34
+ : `<span class="reticle-promo-detail" title="${esc(notice.detail)}">${esc(notice.detail)}</span>`;
35
+ const link = notice.cta === undefined
36
+ ? ''
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>`;
38
+ return {
39
+ id: `${NOTICE_SLIDE_PREFIX}${notice.id}`,
40
+ html: `<div class="reticle-promo-copy">${kicker}<strong class="reticle-promo-title">${esc(notice.title)}</strong>${detail}${link}</div>`,
41
+ };
42
+ }
43
+ export function panelSlides(notices = [], base = DEFAULT_PLATFORM_URL) {
44
+ const valid = notices.flatMap((n) => {
45
+ const parsed = HudNoticeSchema.safeParse(n);
46
+ return parsed.success && !parsed.data.id.startsWith(HARNESS_NOTICE_PREFIX) ? [parsed.data] : [];
47
+ });
48
+ return 0 < valid.length ? valid.map(noticeSlide) : [...valueSlides(base)];
10
49
  }
@@ -5,4 +5,4 @@ export declare const TALK_TEXT: {
5
5
  readonly BOOK: "Book a call";
6
6
  };
7
7
  export declare function talkCardHtml(): string;
8
- export declare const TALK_CSS = "\n.reticle-talk-title{display:block;font-weight:600;font-size:12px;padding-right:18px;}\n.reticle-talk-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}\n.reticle-talk-book{display:inline-block;font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}\n";
8
+ export declare const TALK_CSS: string;
@@ -10,3 +10,8 @@ export declare const LOG_RESULT: {
10
10
  readonly FAIL: "fail";
11
11
  };
12
12
  export type LogResult = (typeof LOG_RESULT)[keyof typeof LOG_RESULT];
13
+ export declare const LOG_ACTOR: {
14
+ readonly AGENT: "agent";
15
+ readonly HARNESS: "harness";
16
+ };
17
+ export type LogActor = (typeof LOG_ACTOR)[keyof typeof LOG_ACTOR];
@@ -5,3 +5,4 @@ export const LOG_KIND = {
5
5
  HUMAN: 'human',
6
6
  };
7
7
  export const LOG_RESULT = { PASS: 'pass', FAIL: 'fail' };
8
+ export const LOG_ACTOR = { AGENT: 'agent', HARNESS: 'harness' };
@@ -0,0 +1,13 @@
1
+ import type { LogActor, LogKind, LogResult } from './presenter-log.js';
2
+ export interface RememberedRow {
3
+ kind: LogKind;
4
+ text: string;
5
+ ts: string;
6
+ at: number;
7
+ actor?: LogActor;
8
+ summary?: true;
9
+ result?: LogResult;
10
+ }
11
+ export declare function readRememberedLog(): RememberedRow[];
12
+ export declare function rememberLog(rows: readonly RememberedRow[]): void;
13
+ export declare function claimLog(owner: string | undefined): boolean;
@@ -0,0 +1,39 @@
1
+ import { ReticleStorageKey } from '../../storage-keys.js';
2
+ const LOG_STORAGE_KEY = ReticleStorageKey.PRESENTER_LOG;
3
+ export function readRememberedLog() {
4
+ try {
5
+ const raw = JSON.parse(globalThis.sessionStorage.getItem(LOG_STORAGE_KEY) ?? '[]');
6
+ return Array.isArray(raw)
7
+ ? raw.filter((row) => 'object' === typeof row &&
8
+ null !== row &&
9
+ 'string' === typeof row.text &&
10
+ 'string' === typeof row.kind &&
11
+ 'string' === typeof row.ts &&
12
+ 'number' === typeof row.at)
13
+ : [];
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ export function rememberLog(rows) {
20
+ try {
21
+ globalThis.sessionStorage.setItem(LOG_STORAGE_KEY, JSON.stringify(rows));
22
+ }
23
+ catch {
24
+ }
25
+ }
26
+ export function claimLog(owner) {
27
+ try {
28
+ const now = owner ?? '';
29
+ const before = globalThis.sessionStorage.getItem(ReticleStorageKey.PRESENTER_LOG_OWNER);
30
+ globalThis.sessionStorage.setItem(ReticleStorageKey.PRESENTER_LOG_OWNER, now);
31
+ if (null === before || before === now)
32
+ return false;
33
+ globalThis.sessionStorage.removeItem(LOG_STORAGE_KEY);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
@@ -2,6 +2,7 @@ export declare const HUD_SURFACE_CLASS = "reticle-hud-surface";
2
2
  export declare const HUD_SURFACE_FILL: string;
3
3
  export declare const HUD_SURFACE_PAINT: string;
4
4
  export declare const HUD_DROP_SHADOW: string;
5
+ export declare const HUD_GLASS_PAINT: string;
5
6
  export declare const HUD_CHROME_CSS: string;
6
7
  export declare const HUD_LOG_WELL_CLASS = "reticle-hud-log-well";
7
8
  export declare const HUD_LOG_WELL_CSS: string;
@@ -2,6 +2,12 @@ export const HUD_SURFACE_CLASS = 'reticle-hud-surface';
2
2
  export const HUD_SURFACE_FILL = `linear-gradient(165deg,#121218 0%,#09090c 42%,#000 100%)`;
3
3
  export const HUD_SURFACE_PAINT = `background:${HUD_SURFACE_FILL};box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 0 0 1px rgba(255,255,255,.09);`;
4
4
  export const HUD_DROP_SHADOW = '0 16px 40px rgba(0,0,0,.58),0 0 0 1px rgba(255,255,255,.08)';
5
+ export const HUD_GLASS_PAINT = [
6
+ 'background:radial-gradient(130% 80% at 50% 0%,color-mix(in srgb,var(--reticle-c-active) 20%,transparent),transparent 62%),',
7
+ 'linear-gradient(180deg,#121a2e,#080b14);',
8
+ 'border:1px solid color-mix(in srgb,var(--reticle-c-active) 30%,rgba(255,255,255,.1));',
9
+ `box-shadow:${HUD_DROP_SHADOW},0 0 64px -12px color-mix(in srgb,var(--reticle-c-active) 75%,transparent);`,
10
+ ].join('');
5
11
  export const HUD_CHROME_CSS = `
6
12
  .${HUD_SURFACE_CLASS}{
7
13
  position:relative;overflow:hidden;
@@ -25,7 +31,7 @@ export const HUD_LOG_WELL_CSS = `
25
31
  as tall as its rows, which left the empty-state line floating near the top of an otherwise
26
32
  empty panel instead of centred in it. */
27
33
  position:relative;display:flex;flex-direction:column;
28
- flex:1 1 auto;height:210px;min-height:96px;max-height:210px;overflow:hidden;
34
+ flex:1 1 auto;height:240px;min-height:120px;max-height:300px;overflow:hidden;
29
35
  contain:layout style paint;
30
36
  margin:0 6px;border-radius:12px;background:transparent;}
31
37
  .${HUD_LOG_WELL_CLASS} > *{position:relative;z-index:1;}`;
@@ -1,11 +1,19 @@
1
1
  import { PresenterMode } from '@reticlehq/core';
2
- import { LOG_KIND, LOG_RESULT, type LogKind, type LogResult } from './log-kinds.js';
3
- export { LOG_KIND, LOG_RESULT };
4
- export type { LogKind, LogResult };
2
+ import { LOG_ACTOR, LOG_KIND, LOG_RESULT, type LogActor, type LogKind, type LogResult } from './log-kinds.js';
3
+ export { LOG_ACTOR, LOG_KIND, LOG_RESULT };
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;
16
+ export declare const LOG_SCROLLABLE_ATTR = "data-reticle-log-scrollable";
9
17
  export declare function settleLogAtLatest(container: HTMLElement): void;
10
18
  export interface LogHandle {
11
19
  result(r: LogResult): void;
@@ -15,4 +23,4 @@ export declare function clearLogRows(container: HTMLElement): void;
15
23
  export declare function clampLogMax(n: number | undefined): number;
16
24
  export declare function humanDuration(ms: number): string;
17
25
  export declare function formatElapsed(ms: number): string;
18
- export declare function appendLogRow(container: HTMLElement, kind: LogKind, text: string, ts: string, logMax: number): LogHandle;
26
+ export declare function appendLogRow(container: HTMLElement, kind: LogKind, text: string, ts: string, logMax: number, actor?: LogActor): LogHandle;
@@ -1,9 +1,9 @@
1
1
  import { PresenterMode } from '@reticlehq/core';
2
- import { LOG_KIND, LOG_RESULT } from './log-kinds.js';
2
+ import { LOG_ACTOR, LOG_KIND, LOG_RESULT, } from './log-kinds.js';
3
3
  import { nativeSetTimeout } from '../../timers/native/native-timers.js';
4
4
  import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, } from '../icons/presenter-icons.js';
5
5
  const DEFAULT_LOG_MAX = 50;
6
- export { LOG_KIND, LOG_RESULT };
6
+ export { LOG_ACTOR, LOG_KIND, LOG_RESULT };
7
7
  const LOG_CHIP = { read: 'READ', act: 'ACT', narration: '', human: '' };
8
8
  const LOG_CHIP_ICON = {
9
9
  read: PresenterIcon.VIEW,
@@ -20,25 +20,44 @@ const LOG_CHIP_MODE = {
20
20
  narration: PresenterMode.IDLE,
21
21
  human: PresenterMode.IDLE,
22
22
  };
23
- const RESULT_GLYPH = { pass: '', fail: 'Fail' };
23
+ const RESULT_GLYPH = { pass: '✓', fail: 'Fail' };
24
+ const HANDOVER_TEXT = {
25
+ agent: 'Your agent is driving',
26
+ harness: 'Reticle Harness is driving',
27
+ };
28
+ export const LAST_ACTOR_ATTR = 'data-reticle-log-actor';
29
+ const DATA_ACTOR = 'data-actor';
30
+ const DATA_STATE = 'data-state';
31
+ const HANDOVER_KIND = 'handover';
24
32
  const RESULT_CLASS = { pass: 'reticle-pass', fail: 'reticle-fail' };
25
33
  export const DATA_RETICLE_LOG = 'data-reticle-log';
26
34
  const DATA_RETICLE_LOG_ROW = 'data-reticle-log-row';
27
35
  export const LOG_TIME_ATTR = 'data-reticle-log-time';
28
36
  const DATA_KIND = 'data-kind';
29
37
  const LOG_TEXT_CLASS = 'reticle-log-text';
38
+ const REPEAT_ATTR = 'data-reticle-repeat';
39
+ const LOG_REPEAT_CLASS = 'reticle-log-repeat';
30
40
  const LOG_RES_CLASS = 'reticle-res';
31
41
  const LOG_CHIP_CLASS = 'reticle-chip';
32
- 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
+ };
33
48
  const LOG_SETTLE_MS = 160;
34
49
  export const LOG_CSS = `
35
50
  [data-reticle-log]{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;pointer-events:auto;touch-action:pan-y;
36
51
  display:flex;flex-direction:column;
37
52
  gap:4px;padding:8px 10px 10px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;
38
53
  background:transparent;}
39
- [data-reticle-log]:empty{align-items:center;justify-content:center;}
40
- [data-reticle-log]:empty::after{content:"${LOG_EMPTY_HINT}";display:block;padding:24px 16px;text-align:center;
41
- 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}";}
42
61
  [data-reticle-log]::-webkit-scrollbar{width:8px;}
43
62
  [data-reticle-log]::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:8px;border:2px solid transparent;background-clip:content-box;}
44
63
  [data-reticle-log]::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.26);background-clip:content-box;}
@@ -61,18 +80,43 @@ export const LOG_CSS = `
61
80
  [data-reticle-log] [data-reticle-log-row] .reticle-chip-label{
62
81
  position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}
63
82
  [data-reticle-log] [data-reticle-log-row][data-kind="read"] .reticle-chip{opacity:.5;}
64
- [${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;}
65
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;}
66
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;}
67
87
  [data-reticle-log] .reticle-res.reticle-pass{display:none;}
68
88
  [data-reticle-log-row][data-kind="human"]{align-self:flex-end;max-width:78%;margin:6px 0 2px;
69
89
  padding:8px 12px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);
70
90
  border-radius:16px 16px 4px 16px;box-shadow:inset 0 1px 0 rgba(255,255,255,.08);}
71
91
  [data-reticle-log-row][data-kind="human"] .reticle-log-text{color:var(--reticle-fg);font-size:12px;line-height:1.45;}
92
+ /* Rows arrive, rather than appear: the person watching should see the log move. */
93
+ [data-reticle-log-row]{animation:reticle-row-in .22s var(--reticle-hud-ease,ease) both;}
94
+ @keyframes reticle-row-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
95
+ @media (prefers-reduced-motion:reduce){[data-reticle-log-row]{animation:none;}}
96
+ /* The step in flight pulses until its verdict lands; then it says how it ended. */
97
+ [data-reticle-log-row][data-state="running"] .reticle-res{display:inline-block;width:6px;height:6px;margin-top:5px;
98
+ border-radius:50%;background:var(--reticle-c-active);opacity:1;animation:reticle-row-pulse 1s ease-in-out infinite;}
99
+ @keyframes reticle-row-pulse{0%,100%{opacity:.35;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}
100
+ [data-reticle-log] .reticle-res.reticle-pass{display:inline;color:#4ade80;font-size:11px;opacity:.9;}
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;}
102
+ /* The Harness's rows wear its own colour, so two drivers never read as one. */
103
+ [data-reticle-log-row][data-actor="harness"]{--reticle-row-accent:#a78bfa;}
104
+ [data-reticle-log-row][data-actor="harness"] .reticle-chip{color:#a78bfa;}
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;}
106
+ [data-reticle-log-row][data-kind="handover"]{align-items:center;gap:8px;margin:8px 0 4px;padding:0;
107
+ font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--reticle-row-accent,var(--reticle-c-active));}
108
+ [data-reticle-log-row][data-kind="handover"]::before,[data-reticle-log-row][data-kind="handover"]::after{
109
+ content:"";flex:1;height:1px;background:color-mix(in srgb,var(--reticle-row-accent,var(--reticle-c-active)) 40%,transparent);}
110
+ [data-reticle-log-row][data-kind="handover"] .reticle-log-text{flex:none;color:inherit;}
111
+ /* What the Harness or the daemon says about a drive reads as a notice, not an aside. */
112
+ [data-reticle-log-row][data-kind="narration"]{font-style:normal;color:var(--reticle-fg);margin:4px 0;padding:7px 10px;
113
+ border-radius:10px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);}
72
114
  `;
73
115
  function scrollLogToLatest(container) {
74
116
  container.scrollTop = container.scrollHeight;
117
+ container.toggleAttribute(LOG_SCROLLABLE_ATTR, container.scrollHeight > container.clientHeight + 1);
75
118
  }
119
+ export const LOG_SCROLLABLE_ATTR = 'data-reticle-log-scrollable';
76
120
  export function settleLogAtLatest(container) {
77
121
  scrollLogToLatest(container);
78
122
  requestAnimationFrame(() => {
@@ -111,10 +155,44 @@ export function humanDuration(ms) {
111
155
  export function formatElapsed(ms) {
112
156
  return humanDuration(ms);
113
157
  }
114
- export function appendLogRow(container, kind, text, ts, logMax) {
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
+ }
176
+ const driven = kind === LOG_KIND.ACT || kind === LOG_KIND.READ;
177
+ if (driven && (container.getAttribute(LAST_ACTOR_ATTR) ?? LOG_ACTOR.AGENT) !== actor) {
178
+ const divider = document.createElement('div');
179
+ divider.setAttribute(DATA_RETICLE_LOG_ROW, '');
180
+ divider.setAttribute(DATA_KIND, HANDOVER_KIND);
181
+ divider.setAttribute(DATA_ACTOR, actor);
182
+ const label = document.createElement('span');
183
+ label.className = LOG_TEXT_CLASS;
184
+ label.textContent = HANDOVER_TEXT[actor];
185
+ divider.appendChild(label);
186
+ container.appendChild(divider);
187
+ }
188
+ if (driven)
189
+ container.setAttribute(LAST_ACTOR_ATTR, actor);
115
190
  const row = document.createElement('div');
116
191
  row.setAttribute(DATA_RETICLE_LOG_ROW, '');
117
192
  row.setAttribute(DATA_KIND, kind);
193
+ row.setAttribute(DATA_ACTOR, actor);
194
+ if (kind === LOG_KIND.ACT)
195
+ row.setAttribute(DATA_STATE, 'running');
118
196
  const tsEl = document.createElement('span');
119
197
  tsEl.setAttribute(LOG_TIME_ATTR, '');
120
198
  tsEl.textContent = ts;
@@ -151,10 +229,22 @@ export function appendLogRow(container, kind, text, ts, logMax) {
151
229
  requestAnimationFrame(() => {
152
230
  scrollLogToLatest(container);
153
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) {
154
243
  return {
155
244
  result: (r) => {
245
+ row.setAttribute(DATA_STATE, r);
156
246
  if (r === LOG_RESULT.PASS) {
157
- resEl.textContent = '';
247
+ resEl.textContent = RESULT_GLYPH.pass;
158
248
  resEl.className = `${LOG_RES_CLASS} ${RESULT_CLASS.pass}`;
159
249
  return;
160
250
  }
@@ -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
+ }
@@ -7,9 +7,13 @@ export declare const REPORT_LINKS: {
7
7
  };
8
8
  export declare const REPORT_TEXT: {
9
9
  readonly TITLE: "Impact";
10
+ readonly COVERAGE: "Reticle Coverage";
11
+ readonly COVERAGE_HELP: "How much of this app Reticle has reached, touched and proved, from the coverage ledger in .reticle";
10
12
  readonly PROJECT: "This project";
11
- readonly GLOBAL: "Everything on this machine";
12
- readonly HERO_DEFECTS: "checks Reticle refused to pass";
13
+ readonly GLOBAL: "All projects";
14
+ readonly GLOBAL_TITLE: "Every project on this computer";
15
+ readonly SCOPE_LABEL: "Show impact for";
16
+ readonly heroDefects: (n: number) => string;
13
17
  readonly VERDICTS: "Verdicts";
14
18
  readonly PASSED: "passed";
15
19
  readonly FAILED: "failed";
@@ -27,20 +31,35 @@ export declare const REPORT_TEXT: {
27
31
  readonly CHART: "Verdicts, last 30 days";
28
32
  readonly DEFECTS: "What broke";
29
33
  readonly DEFECTS_MORE: "Manage all of them on the dashboard — triage, assign, and push to GitHub";
34
+ readonly DEFECTS_SEE_ALL: "See all on the dashboard";
35
+ readonly DEFECTS_SEE_ALL_TITLE: "Sign up free, link this project, and see every bug Reticle caught";
30
36
  readonly DEFECT_LINK_TITLE: "Open on the dashboard to triage or push to GitHub";
31
37
  readonly LOCAL_ONLY: "This record stops at this machine.";
32
- readonly LOCAL_ONLY_ACTION: "reticle login";
38
+ readonly LOCAL_ONLY_ACTION: "reticle connect";
33
39
  readonly LOCAL_ONLY_TAIL: "keeps it, and lets a team see it.";
34
40
  readonly UNLINKED: "Signed in, but this repo is not linked.";
35
- readonly UNLINKED_ACTION: "reticle link";
41
+ readonly UNLINKED_ACTION: "reticle connect";
36
42
  readonly UNLINKED_TAIL: "sends this record to your workspace.";
37
43
  readonly DEFECTS_NONE: "Nothing has failed a declared consequence yet.";
38
44
  readonly EMPTY: "Nothing recorded yet. Drive the app once and this fills in.";
39
45
  readonly SHARE: "Share";
40
46
  readonly COPY: "Copy";
41
47
  readonly COPIED: "Copied";
42
- 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";
43
61
  };
62
+ export declare const CAPABILITY_WORDS: Readonly<Record<string, string>>;
44
63
  export declare function compactNumber(n: number): string;
45
64
  export declare function compactDuration(ms: number): string;
46
65
  export declare function buildShareText(scope: ImpactScope, projectName?: string): string;
@@ -48,3 +67,7 @@ export declare function buildXShareUrl(text: string): string;
48
67
  export declare function buildLinkedInShareUrl(): string;
49
68
  export declare function buildReferralText(): string;
50
69
  export declare function parseImpactSnapshot(value: unknown): ImpactSnapshot | undefined;
70
+ export declare const COVERAGE_LEVELS: readonly {
71
+ key: string;
72
+ label: string;
73
+ }[];
@@ -8,9 +8,13 @@ export const REPORT_LINKS = {
8
8
  const SHARE_VIA_HANDLE = '';
9
9
  export const REPORT_TEXT = {
10
10
  TITLE: 'Impact',
11
+ COVERAGE: 'Reticle Coverage',
12
+ COVERAGE_HELP: 'How much of this app Reticle has reached, touched and proved, from the coverage ledger in .reticle',
11
13
  PROJECT: 'This project',
12
- GLOBAL: 'Everything on this machine',
13
- HERO_DEFECTS: 'checks Reticle refused to pass',
14
+ GLOBAL: 'All projects',
15
+ GLOBAL_TITLE: 'Every project on this computer',
16
+ SCOPE_LABEL: 'Show impact for',
17
+ heroDefects: (n) => `failed check${1 === n ? '' : 's'} caught before you shipped`,
14
18
  VERDICTS: 'Verdicts',
15
19
  PASSED: 'passed',
16
20
  FAILED: 'failed',
@@ -28,19 +32,43 @@ export const REPORT_TEXT = {
28
32
  CHART: 'Verdicts, last 30 days',
29
33
  DEFECTS: 'What broke',
30
34
  DEFECTS_MORE: 'Manage all of them on the dashboard — triage, assign, and push to GitHub',
35
+ DEFECTS_SEE_ALL: 'See all on the dashboard',
36
+ DEFECTS_SEE_ALL_TITLE: 'Sign up free, link this project, and see every bug Reticle caught',
31
37
  DEFECT_LINK_TITLE: 'Open on the dashboard to triage or push to GitHub',
32
38
  LOCAL_ONLY: 'This record stops at this machine.',
33
- LOCAL_ONLY_ACTION: 'reticle login',
39
+ LOCAL_ONLY_ACTION: 'reticle connect',
34
40
  LOCAL_ONLY_TAIL: 'keeps it, and lets a team see it.',
35
41
  UNLINKED: 'Signed in, but this repo is not linked.',
36
- UNLINKED_ACTION: 'reticle link',
42
+ UNLINKED_ACTION: 'reticle connect',
37
43
  UNLINKED_TAIL: 'sends this record to your workspace.',
38
44
  DEFECTS_NONE: 'Nothing has failed a declared consequence yet.',
39
45
  EMPTY: 'Nothing recorded yet. Drive the app once and this fills in.',
40
46
  SHARE: 'Share',
41
47
  COPY: 'Copy',
42
48
  COPIED: 'Copied',
43
- 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',
44
72
  };
45
73
  export function compactNumber(n) {
46
74
  if (n < 1000)
@@ -93,3 +121,10 @@ export function parseImpactSnapshot(value) {
93
121
  const parsed = ImpactSnapshotSchema.safeParse(value);
94
122
  return parsed.success ? parsed.data : undefined;
95
123
  }
124
+ export const COVERAGE_LEVELS = [
125
+ { key: 'reached', label: 'routes reached' },
126
+ { key: 'touched', label: 'controls touched' },
127
+ { key: 'proved', label: 'controls proved' },
128
+ { key: 'branched', label: 'write failures driven' },
129
+ { key: 'executed', label: 'code executed' },
130
+ ];