@reticlehq/browser 3.6.0 → 3.8.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 (129) hide show
  1. package/dist/actions/action-report.d.ts +64 -0
  2. package/dist/actions/action-report.js +87 -0
  3. package/dist/actions/actions-dom.d.ts +5 -0
  4. package/dist/actions/actions-dom.js +32 -4
  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 +2 -37
  8. package/dist/actions/actions.js +56 -80
  9. package/dist/actions/appeared-text.js +5 -2
  10. package/dist/actions/click-target.d.ts +1 -0
  11. package/dist/actions/click-target.js +14 -0
  12. package/dist/actions/danger-context.d.ts +4 -1
  13. package/dist/actions/danger-context.js +45 -4
  14. package/dist/actions/value-input.d.ts +1 -0
  15. package/dist/actions/value-input.js +11 -0
  16. package/dist/activate.d.ts +14 -0
  17. package/dist/activate.js +43 -0
  18. package/dist/busy.d.ts +3 -0
  19. package/dist/busy.js +42 -0
  20. package/dist/commands/commands.js +49 -17
  21. package/dist/connect-options.d.ts +12 -1
  22. package/dist/connect-options.js +18 -1
  23. package/dist/dom/a11y.d.ts +2 -0
  24. package/dist/dom/a11y.js +353 -80
  25. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  26. package/dist/dom/addressing/coverage-markers.js +13 -0
  27. package/dist/dom/chart.d.ts +3 -2
  28. package/dist/dom/chart.js +3 -2
  29. package/dist/dom/dom-ignore.d.ts +8 -0
  30. package/dist/dom/dom-ignore.js +57 -2
  31. package/dist/dom/layout-audit.d.ts +15 -0
  32. package/dist/dom/layout-audit.js +208 -0
  33. package/dist/dom/paint-context.d.ts +15 -0
  34. package/dist/dom/paint-context.js +78 -0
  35. package/dist/dom/query.d.ts +2 -1
  36. package/dist/dom/query.js +163 -38
  37. package/dist/dom/render-page.d.ts +12 -0
  38. package/dist/dom/render-page.js +135 -0
  39. package/dist/dom/shadow-registry.d.ts +1 -0
  40. package/dist/dom/shadow-registry.js +27 -0
  41. package/dist/dom/snapshot.d.ts +5 -0
  42. package/dist/dom/snapshot.js +135 -36
  43. package/dist/dom/stable-selector.d.ts +1 -0
  44. package/dist/dom/stable-selector.js +19 -0
  45. package/dist/dom/text-match.d.ts +12 -0
  46. package/dist/dom/text-match.js +115 -0
  47. package/dist/dom/theme.d.ts +1 -0
  48. package/dist/dom/theme.js +21 -9
  49. package/dist/dom/virtualized.js +5 -1
  50. package/dist/index.d.ts +2 -1
  51. package/dist/index.js +5 -0
  52. package/dist/observers/blind-spots.js +3 -2
  53. package/dist/observers/canvas.d.ts +18 -0
  54. package/dist/observers/canvas.js +76 -0
  55. package/dist/observers/dom.js +96 -32
  56. package/dist/observers/field.d.ts +11 -0
  57. package/dist/observers/field.js +116 -30
  58. package/dist/observers/install-all.js +4 -0
  59. package/dist/observers/media.d.ts +2 -0
  60. package/dist/observers/media.js +32 -0
  61. package/dist/observers/net-detail/network-body.d.ts +2 -0
  62. package/dist/observers/net-detail/network-body.js +8 -1
  63. package/dist/observers/network.js +82 -13
  64. package/dist/observers/perf.js +53 -1
  65. package/dist/observers/route.d.ts +2 -0
  66. package/dist/observers/route.js +39 -9
  67. package/dist/presenter/carousel/carousel.js +2 -7
  68. package/dist/presenter/carousel/panel-slides.d.ts +2 -4
  69. package/dist/presenter/carousel/panel-slides.js +17 -15
  70. package/dist/presenter/chrome/log-memory.d.ts +1 -0
  71. package/dist/presenter/chrome/presenter-log.d.ts +7 -0
  72. package/dist/presenter/chrome/presenter-log.js +48 -8
  73. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  74. package/dist/presenter/chrome/presenter-morph.js +34 -0
  75. package/dist/presenter/chrome/presenter-report-copy.d.ts +12 -4
  76. package/dist/presenter/chrome/presenter-report-copy.js +12 -4
  77. package/dist/presenter/hud-telemetry.js +55 -4
  78. package/dist/presenter/presenter-account-styles.js +3 -3
  79. package/dist/presenter/presenter-account.d.ts +27 -5
  80. package/dist/presenter/presenter-account.js +83 -6
  81. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  82. package/dist/presenter/presenter-agent-link.js +62 -0
  83. package/dist/presenter/presenter-chat-views.d.ts +25 -4
  84. package/dist/presenter/presenter-chat-views.js +339 -72
  85. package/dist/presenter/presenter-config.d.ts +3 -0
  86. package/dist/presenter/presenter-config.js +2 -0
  87. package/dist/presenter/presenter-controls.d.ts +0 -1
  88. package/dist/presenter/presenter-controls.js +13 -91
  89. package/dist/presenter/presenter-design-tokens.js +1 -1
  90. package/dist/presenter/presenter-frame.d.ts +3 -2
  91. package/dist/presenter/presenter-frame.js +31 -24
  92. package/dist/presenter/presenter-harness-row.d.ts +56 -0
  93. package/dist/presenter/presenter-harness-row.js +170 -0
  94. package/dist/presenter/presenter-personas.d.ts +35 -0
  95. package/dist/presenter/presenter-personas.js +95 -0
  96. package/dist/presenter/presenter-plan.js +25 -24
  97. package/dist/presenter/presenter-report-styles.js +23 -22
  98. package/dist/presenter/presenter-report.js +29 -13
  99. package/dist/presenter/presenter-run-state.js +2 -1
  100. package/dist/presenter/presenter-settings-styles.js +8 -4
  101. package/dist/presenter/presenter-settings.d.ts +4 -4
  102. package/dist/presenter/presenter-settings.js +66 -28
  103. package/dist/presenter/presenter-shell-styles.js +12 -3
  104. package/dist/presenter/presenter-shell.d.ts +11 -3
  105. package/dist/presenter/presenter-shell.js +60 -22
  106. package/dist/presenter/presenter-styles.js +0 -2
  107. package/dist/presenter/presenter-workspace.js +10 -3
  108. package/dist/presenter/presenter.d.ts +3 -2
  109. package/dist/presenter/presenter.js +107 -25
  110. package/dist/registry/declared-platform.js +2 -0
  111. package/dist/registry/store-adapters.d.ts +5 -2
  112. package/dist/registry/store-adapters.js +40 -10
  113. package/dist/reticle-inject.js +771 -703
  114. package/dist/reticle.d.ts +4 -2
  115. package/dist/reticle.js +72 -8
  116. package/dist/security/page-location.d.ts +2 -0
  117. package/dist/security/page-location.js +7 -0
  118. package/dist/security/serialization.js +1 -1
  119. package/dist/storage-keys.d.ts +4 -2
  120. package/dist/storage-keys.js +8 -3
  121. package/dist/timers/native/native-timers.d.ts +1 -0
  122. package/dist/timers/native/native-timers.js +5 -0
  123. package/dist/transport/transport.d.ts +1 -0
  124. package/dist/transport/transport.js +23 -3
  125. package/dist/transport/unreachable-message.d.ts +1 -1
  126. package/dist/transport/unreachable-message.js +3 -5
  127. package/package.json +6 -2
  128. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  129. package/dist/presenter/carousel/offer-card.js +0 -56
@@ -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,17 +35,25 @@ 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 COPY_PROMPT: "Copy prompt for your coding agent";
53
+ readonly SHARE_LABEL: "Share";
54
+ readonly POST_X: "X";
55
+ readonly POST_IN: "LinkedIn";
56
+ readonly REFER: "Invite a friend";
49
57
  };
50
58
  export declare function compactNumber(n: number): string;
51
59
  export declare function compactDuration(ms: number): 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,25 @@ 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
+ COPY_PROMPT: 'Copy prompt for your coding agent',
54
+ SHARE_LABEL: 'Share',
55
+ POST_X: 'X',
56
+ POST_IN: 'LinkedIn',
57
+ REFER: 'Invite a friend',
50
58
  };
51
59
  export function compactNumber(n) {
52
60
  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) => HudPanel.CHAT === at.panel,
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;
@@ -1,15 +1,27 @@
1
+ import { CreditKind } from '@reticlehq/core';
2
+ import { TRIAL_OFFER } from '@reticlehq/core/hud';
1
3
  import { hiIconHtml, PRESENTER_ICON_SIZE, PresenterIcon } from './icons/presenter-icons.js';
2
4
  import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
5
+ import { morphHtml } from './chrome/presenter-morph.js';
3
6
  export const ACCOUNT_ROOT_ATTR = 'data-reticle-account';
4
7
  export const ACCOUNT_SIGNIN_ATTR = 'data-reticle-account-signin';
5
8
  export const SYNC_BTN_ATTR = 'data-reticle-sync-now';
6
9
  export const ACCOUNT_TRIGGER_ATTR = 'data-reticle-account-trigger';
7
10
  export const ACCOUNT_MENU_ATTR = 'data-reticle-account-menu';
8
11
  export const ACCOUNT_SIGNOUT_ATTR = 'data-reticle-account-signout';
12
+ export const TEAM_OPEN_ATTR = 'data-reticle-team-open';
13
+ export const TEAM_SAVE_ATTR = 'data-reticle-team-save';
14
+ export const TEAM_SLOT_ATTR = 'data-reticle-team';
15
+ export const TEAM_TEXT = {
16
+ OPEN: 'Open on dashboard ↗',
17
+ OPEN_TITLE: "This project's runs, kept for your team on the Reticle dashboard",
18
+ SAVE_LINK: 'Save runs for your team',
19
+ SAVE_TITLE: `Keep every run, flow and Harness result on a dashboard your team shares. On Reticle, ${TRIAL_OFFER}.`,
20
+ };
9
21
  export const ACCOUNT_TEXT = {
10
22
  SIGNED_OUT: 'Sign in',
11
- SIGNIN_TITLE: 'Sign in through your browser. Also copies `reticle login`, for a terminal',
12
- SIGNIN_COMMAND: 'reticle login',
23
+ SIGNIN_TITLE: 'Sign in through your browser. Also copies `reticle connect`, for a terminal',
24
+ SIGNIN_COMMAND: 'reticle connect',
13
25
  DASHBOARD_TITLE: 'Open this project on the dashboard',
14
26
  SYNC_TITLE: 'Push to the dashboard now, instead of waiting for the next sync',
15
27
  COPIED: 'Copied',
@@ -18,8 +30,8 @@ export const ACCOUNT_TEXT = {
18
30
  TRIGGER_TITLE: 'Your Reticle account',
19
31
  SIGNED_IN_AS: 'Signed in',
20
32
  DASHBOARD_ACTION: 'Open dashboard',
21
- LINK_HINT: 'Not linked — run `reticle link` to keep a record on the dashboard',
22
- LINK_COMMAND: 'reticle link',
33
+ LINK_HINT: 'Not linked — run `reticle connect` to keep a record on the dashboard',
34
+ LINK_COMMAND: 'reticle connect',
23
35
  SIGNOUT_ACTION: 'Copy sign-out command',
24
36
  SIGNOUT_TITLE: 'Copy `reticle logout` to run in your terminal',
25
37
  SIGNOUT_COMMAND: 'reticle logout',
@@ -28,6 +40,11 @@ export const ACCOUNT_TEXT = {
28
40
  HOST_LABEL: 'Host',
29
41
  VERDICTS_LABEL: 'Verdicts',
30
42
  DEFECTS_LABEL: 'Defects found',
43
+ PLAN_LABEL: 'Plan',
44
+ CREDITS_LABEL: 'Credits',
45
+ HARNESS_RUNS_LABEL: 'Harness runs',
46
+ MEMORY_LABEL: 'Memory',
47
+ ON_PLATFORM_LABEL: 'Runs on dashboard',
31
48
  };
32
49
  const ACCOUNT_COPIED_MS = 1200;
33
50
  export function accountInitials(org) {
@@ -70,12 +87,20 @@ function accountMenuHtml(account, details) {
70
87
  `<span class="reticle-account-org">${org.length > 0 ? esc(org) : ACCOUNT_TEXT.SIGNED_IN_AS}</span>` +
71
88
  (account.email !== undefined && account.email.length > 0
72
89
  ? `<span class="reticle-account-email" title="${esc(account.email)}">${esc(account.email)}</span>`
73
- : `<span class="reticle-account-email">${ACCOUNT_TEXT.SIGNED_IN_AS}</span>`) +
90
+ :
91
+ org.length > 0
92
+ ? `<span class="reticle-account-email">${ACCOUNT_TEXT.SIGNED_IN_AS}</span>`
93
+ : '') +
74
94
  `</span></div>`;
75
95
  const rows = detailRow(ACCOUNT_TEXT.HOST_LABEL, account.host) +
76
96
  detailRow(ACCOUNT_TEXT.PROJECT_LABEL, details.projectName) +
77
97
  detailRow(ACCOUNT_TEXT.VERDICTS_LABEL, details.verdicts !== undefined ? String(details.verdicts) : undefined) +
78
- detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined);
98
+ detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined) +
99
+ detailRow(ACCOUNT_TEXT.ON_PLATFORM_LABEL, details.onPlatform) +
100
+ detailRow(ACCOUNT_TEXT.PLAN_LABEL, details.plan) +
101
+ detailRow(ACCOUNT_TEXT.CREDITS_LABEL, details.credits) +
102
+ detailRow(ACCOUNT_TEXT.HARNESS_RUNS_LABEL, details.harnessRuns) +
103
+ detailRow(ACCOUNT_TEXT.MEMORY_LABEL, details.memory);
79
104
  const linked = details.dashboardUrl !== undefined && isSafeDashboardUrl(details.dashboardUrl);
80
105
  const dashboard = linked
81
106
  ? `<a class="reticle-account-action" data-reticle-account-dashboard href="${esc(details.dashboardUrl ?? '')}" target="_blank" rel="noreferrer noopener" title="${ACCOUNT_TEXT.DASHBOARD_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.HELP)}<span>${ACCOUNT_TEXT.DASHBOARD_ACTION}</span></a>`
@@ -88,6 +113,58 @@ function accountMenuHtml(account, details) {
88
113
  `<div class="reticle-account-actions">${dashboard}${signOut}</div>` +
89
114
  `</div>`);
90
115
  }
116
+ const PLAN_NAME = {
117
+ [CreditKind.FREE]: 'Free',
118
+ [CreditKind.TRIAL]: 'Trial',
119
+ [CreditKind.PAID]: 'Pro',
120
+ };
121
+ const plural = (n, one, many) => `${String(n)} ${1 === n ? one : many}`;
122
+ export function platformDetails(config, sync) {
123
+ const plan = config?.plan;
124
+ const onPlatform = sync?.['onPlatform'];
125
+ return {
126
+ ...(plan === undefined
127
+ ? {}
128
+ : {
129
+ plan: [
130
+ PLAN_NAME[plan.kind],
131
+ plan.daysLeft === undefined ? '' : `${plural(plan.daysLeft, 'day', 'days')} left`,
132
+ plan.seats === undefined ? '' : plural(plan.seats, 'seat', 'seats'),
133
+ ]
134
+ .filter((part) => part.length > 0)
135
+ .join(' · '),
136
+ }),
137
+ ...(config?.credits === undefined
138
+ ? {}
139
+ : {
140
+ credits: `${Math.max(0, config.credits.limit - config.credits.used).toLocaleString('en-US')} of ${config.credits.limit.toLocaleString('en-US')} left`,
141
+ }),
142
+ ...(config?.harnessRuns === undefined ? {} : { harnessRuns: String(config.harnessRuns) }),
143
+ ...(config?.memoryEntries === undefined
144
+ ? {}
145
+ : { memory: plural(config.memoryEntries, 'entry', 'entries') }),
146
+ ...('number' === typeof onPlatform ? { onPlatform: String(onPlatform) } : {}),
147
+ };
148
+ }
149
+ export function teamLinkHtml(account, dashboardUrl) {
150
+ if (account === undefined || !account.signedIn)
151
+ return '';
152
+ if (dashboardUrl !== undefined && isSafeDashboardUrl(dashboardUrl))
153
+ return `<a ${TEAM_OPEN_ATTR} class="reticle-team-link" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener" title="${esc(TEAM_TEXT.OPEN_TITLE)}">${TEAM_TEXT.OPEN}</a>`;
154
+ return `<button type="button" ${TEAM_SAVE_ATTR} class="reticle-team-link" title="${esc(TEAM_TEXT.SAVE_TITLE)}"><span>${TEAM_TEXT.SAVE_LINK}</span></button>`;
155
+ }
156
+ export function paintAccountSlot(slot, html) {
157
+ const open = null !== slot.querySelector(`[${ACCOUNT_MENU_ATTR}]:not([hidden])`);
158
+ morphHtml(slot, html);
159
+ if (!open)
160
+ return;
161
+ const menu = slot.querySelector(`[${ACCOUNT_MENU_ATTR}]`);
162
+ if (null === menu)
163
+ return;
164
+ menu.removeAttribute('hidden');
165
+ menu.setAttribute('aria-hidden', 'false');
166
+ slot.querySelector(`[${ACCOUNT_TRIGGER_ATTR}]`)?.setAttribute('aria-expanded', 'true');
167
+ }
91
168
  export function accountCapsuleHtml(account, dashboardUrl, offerSignIn = false) {
92
169
  return accountControlHtml(account, { dashboardUrl }, offerSignIn);
93
170
  }
@@ -0,0 +1,9 @@
1
+ import { type AgentLink } from '@reticlehq/core';
2
+ export declare const AGENT_LINK_HTML: string;
3
+ export declare const AGENT_LINK_CSS = "\n[data-reticle-chat-panel] .reticle-agent-spot{flex:none;display:flex;flex-direction:column;gap:3px;}\n[data-reticle-chat-panel] .reticle-agent-spot[hidden]{display:none;}\n[data-reticle-chat-panel] .reticle-agent-row{display:flex;align-items:center;gap:8px;min-width:0;min-height:30px;padding:0 var(--reticle-hud-space-2);}\n[data-reticle-chat-panel] .reticle-agent-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--reticle-hud-text-faint);}\n[data-reticle-chat-panel] .reticle-agent-row[data-connected=\"1\"] .reticle-agent-dot{background:#4ade80;box-shadow:0 0 0 3px rgba(74,222,128,.15);}\n[data-reticle-chat-panel] .reticle-agent-said{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text-muted);}\n[data-reticle-chat-panel] .reticle-agent-copy:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}\n[data-reticle-chat-panel] .reticle-agent-copy{flex:none;height:22px;padding:0 9px;border:1px solid var(--reticle-hud-border-strong);border-radius:11px;background:transparent;color:var(--reticle-hud-text);font:inherit;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;}\n[data-reticle-chat-panel] .reticle-agent-copy:hover{border-color:var(--reticle-hud-brand);color:var(--reticle-hud-brand);}\n[data-reticle-chat-panel] .reticle-agent-copy[hidden]{display:none;}\n";
4
+ export declare class AgentLinkView {
5
+ #private;
6
+ mount(root: HTMLElement, signal: AbortSignal): void;
7
+ teardown(): void;
8
+ paint(link: AgentLink | undefined): void;
9
+ }
@@ -0,0 +1,62 @@
1
+ import {} from '@reticlehq/core';
2
+ import { REPORT_TEXT } from './chrome/presenter-report-copy.js';
3
+ const COPIED_FLASH_MS = 1600;
4
+ const TEXT = {
5
+ NONE: 'No coding agent connected',
6
+ named: (names) => `${names} connected`,
7
+ COPY: 'Copy prompt',
8
+ prompt: (url) => `Use Reticle to check ${url} in my running app, then tell me the verdict.`,
9
+ };
10
+ export const AGENT_LINK_HTML = `<div data-reticle-agent-spot class="reticle-agent-spot" hidden>
11
+ <div class="reticle-agent-row"><span data-reticle-agent-dot class="reticle-agent-dot" aria-hidden="true"></span><span data-reticle-agent-said class="reticle-agent-said">${TEXT.NONE}</span><button type="button" data-reticle-agent-copy class="reticle-agent-copy" aria-label="${REPORT_TEXT.COPY_PROMPT}" title="${REPORT_TEXT.COPY_PROMPT}">${TEXT.COPY}</button></div>
12
+ </div>`;
13
+ export const AGENT_LINK_CSS = `
14
+ [data-reticle-chat-panel] .reticle-agent-spot{flex:none;display:flex;flex-direction:column;gap:3px;}
15
+ [data-reticle-chat-panel] .reticle-agent-spot[hidden]{display:none;}
16
+ [data-reticle-chat-panel] .reticle-agent-row{display:flex;align-items:center;gap:8px;min-width:0;min-height:30px;padding:0 var(--reticle-hud-space-2);}
17
+ [data-reticle-chat-panel] .reticle-agent-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--reticle-hud-text-faint);}
18
+ [data-reticle-chat-panel] .reticle-agent-row[data-connected="1"] .reticle-agent-dot{background:#4ade80;box-shadow:0 0 0 3px rgba(74,222,128,.15);}
19
+ [data-reticle-chat-panel] .reticle-agent-said{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text-muted);}
20
+ [data-reticle-chat-panel] .reticle-agent-copy:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}
21
+ [data-reticle-chat-panel] .reticle-agent-copy{flex:none;height:22px;padding:0 9px;border:1px solid var(--reticle-hud-border-strong);border-radius:11px;background:transparent;color:var(--reticle-hud-text);font:inherit;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;}
22
+ [data-reticle-chat-panel] .reticle-agent-copy:hover{border-color:var(--reticle-hud-brand);color:var(--reticle-hud-brand);}
23
+ [data-reticle-chat-panel] .reticle-agent-copy[hidden]{display:none;}
24
+ `;
25
+ export class AgentLinkView {
26
+ #root;
27
+ mount(root, signal) {
28
+ this.#root = root;
29
+ root.querySelector('[data-reticle-agent-copy]')?.addEventListener('click', (event) => {
30
+ const button = event.currentTarget;
31
+ void navigator.clipboard
32
+ ?.writeText(TEXT.prompt(location.href))
33
+ .catch(() => undefined)
34
+ .finally(() => {
35
+ if (!(button instanceof HTMLElement))
36
+ return;
37
+ button.textContent = REPORT_TEXT.COPIED;
38
+ window.setTimeout(() => (button.textContent = REPORT_TEXT.COPY_PROMPT), COPIED_FLASH_MS);
39
+ });
40
+ }, { signal });
41
+ }
42
+ teardown() {
43
+ this.#root = undefined;
44
+ }
45
+ paint(link) {
46
+ const root = this.#root;
47
+ const spot = root?.querySelector('[data-reticle-agent-spot]');
48
+ if (root === undefined || null === spot || spot === undefined)
49
+ return;
50
+ spot.hidden = link === undefined;
51
+ if (link === undefined)
52
+ return;
53
+ const connected = 0 < link.agents.length;
54
+ spot.querySelector('.reticle-agent-row')?.setAttribute('data-connected', connected ? '1' : '0');
55
+ const said = spot.querySelector('[data-reticle-agent-said]');
56
+ if (said !== null)
57
+ said.textContent = connected ? TEXT.named(link.agents.join(', ')) : TEXT.NONE;
58
+ const copy = spot.querySelector('[data-reticle-agent-copy]');
59
+ if (copy !== null)
60
+ copy.hidden = connected;
61
+ }
62
+ }
@@ -1,21 +1,42 @@
1
- import { type AccountState, type HarnessConfig } from '@reticlehq/core';
1
+ import { type AccountState, type HarnessConfig, type AgentLink, type HarnessDrive, type LastDrive } from '@reticlehq/core';
2
2
  import type { AnnotationItem } from '../review/annotator.js';
3
- export type ChatView = 'activity' | 'flows' | 'annotations';
3
+ export type ChatView = 'activity' | 'flows' | 'annotations' | 'harness';
4
4
  export declare function platformBase(config: HarnessConfig | undefined, account: AccountState): string;
5
- export declare const CHAT_VIEWS_HTML = "\n <div data-reticle-harness-spot class=\"reticle-harness-spot\" hidden></div>";
5
+ export interface HudDriveHost {
6
+ run(persona?: string): void;
7
+ stop(): void;
8
+ setHarness(on: boolean): void;
9
+ link(): void;
10
+ }
11
+ export declare const CHAT_VIEWS_HTML: string;
6
12
  export declare const FLOWS_PAGE_HTML: string;
13
+ export declare const NAV_LABEL: {
14
+ readonly LOG: "Log";
15
+ readonly FLOWS: "Flows";
16
+ readonly NOTES: "Notes";
17
+ readonly IMPACT: "Impact";
18
+ readonly HARNESS: "Harness";
19
+ readonly SETTINGS: "Settings";
20
+ readonly HIDE: "Hide";
21
+ };
22
+ export declare const navLabelHtml: (text: string) => string;
7
23
  export declare const CHAT_VIEWS_NAV_HTML: string;
24
+ export declare const HARNESS_PAGE_HTML: string;
8
25
  export declare const ANNOTATIONS_HTML: string;
9
26
  export declare const CHAT_VIEWS_CSS: string;
10
27
  export declare class ChatViews {
11
28
  #private;
12
- constructor(onHarness: (enabled: boolean) => void, onImpact: () => void, onOpenChat: (view: ChatView) => void);
29
+ constructor(onImpact: () => void, onOpenChat: (view: ChatView) => void, drive?: HudDriveHost);
13
30
  mount(root: HTMLElement): void;
14
31
  teardown(): void;
15
32
  open(view: ChatView): void;
16
33
  closePage(): void;
34
+ setProjectId(projectId: string | undefined): void;
35
+ paintAgent(link: AgentLink | undefined): void;
17
36
  paintAccount(account: AccountState | undefined): void;
37
+ paintDrive(drive: HarnessDrive | undefined, last?: LastDrive): void;
18
38
  paintHarness(config: HarnessConfig | undefined): void;
19
39
  paintImpact(verdicts: number): void;
40
+ setImpactOpen(open: boolean): void;
20
41
  paintAnnotations(items: readonly AnnotationItem[]): void;
21
42
  }