@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,15 +1,24 @@
1
- import { DEFAULT_PLATFORM_URL } from '@reticlehq/core';
1
+ import { DEFAULT_PLATFORM_URL, } from '@reticlehq/core';
2
+ import { PLAN_HTML } from './presenter-plan.js';
2
3
  import { marksForAgent } from '../review/marks-for-agent.js';
3
4
  import { ANNOTATE_BTN_ATTR, CHAT_TOGGLE_ATTR, CLEAR_MARKS_ATTR, MARKERS_BTN_ATTR, } from './presenter-config.js';
4
5
  import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
5
6
  import { HUD_GLASS_PAINT } from './chrome/presenter-hud-chrome.js';
6
- import { creditsLeft } from './presenter-settings.js';
7
+ import { LAST_ACTOR_ATTR, LOG_ACTOR } from './chrome/presenter-log.js';
8
+ import { HARNESS_COPY_PROMPT_ATTR, HARNESS_LINK_ATTR, HARNESS_LOG_ATTR, HARNESS_OPEN_ATTR, HARNESS_ROW_TEXT, HARNESS_SWITCH_ON_ATTR, HARNESS_TOGGLE_ATTR, creditsShort, harnessFootHtml, harnessRowHtml, HARNESS_ROW_CSS, } from './presenter-harness-row.js';
7
9
  import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
8
- import { ReticleStorageKey } from '../storage-keys.js';
10
+ import { morphHtml } from './chrome/presenter-morph.js';
11
+ import { IMPACT_SEEN_KEY_PREFIX, ReticleStorageKey } from '../storage-keys.js';
12
+ import { DEFAULT_PERSONA, PERSONA_PICK_ID, knownPick, personaText, readPick, savePick, } from './presenter-personas.js';
13
+ import { AGENT_LINK_CSS, AGENT_LINK_HTML, AgentLinkView } from './presenter-agent-link.js';
14
+ import { ACCOUNT_SIGNIN_ATTR, TEAM_SAVE_ATTR, TEAM_SLOT_ATTR } from './presenter-account.js';
15
+ const PAGE_VIEWS = ['flows', 'annotations', 'harness'];
9
16
  const HISTORY_KEY = ReticleStorageKey.ANNOTATION_HISTORY;
10
17
  const HISTORY_LIMIT = 200;
11
- const HARNESS_SETUP_PATH = '/settings?group=project#model';
18
+ const HARNESS_SETUP_PATH = '/settings?group=project';
19
+ const HARNESS_SETUP_HASH = '#model';
12
20
  const HARNESS_PLAN_PATH = '/settings?group=billing';
21
+ const HARNESS_STARTING_MS = 20_000;
13
22
  export function platformBase(config, account) {
14
23
  const base = [config?.platformUrl, account.host].find((url) => url !== undefined && isSafeDashboardUrl(url));
15
24
  return esc((base ?? DEFAULT_PLATFORM_URL).replace(/\/+$/, ''));
@@ -37,6 +46,21 @@ function readHistory() {
37
46
  return [];
38
47
  }
39
48
  }
49
+ function readImpactSeen(projectId) {
50
+ try {
51
+ return Number(localStorage.getItem(`${IMPACT_SEEN_KEY_PREFIX}${projectId ?? ''}`) ?? 0) || 0;
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ function saveImpactSeen(projectId, verdicts) {
58
+ try {
59
+ localStorage.setItem(`${IMPACT_SEEN_KEY_PREFIX}${projectId ?? ''}`, String(verdicts));
60
+ }
61
+ catch {
62
+ }
63
+ }
40
64
  function saveHistory(items) {
41
65
  try {
42
66
  localStorage.setItem(HISTORY_KEY, JSON.stringify(items.slice(0, HISTORY_LIMIT)));
@@ -44,31 +68,52 @@ function saveHistory(items) {
44
68
  catch {
45
69
  }
46
70
  }
47
- export const CHAT_VIEWS_HTML = `
71
+ export const CHAT_VIEWS_HTML = `${AGENT_LINK_HTML}
48
72
  <div data-reticle-harness-spot class="reticle-harness-spot" hidden></div>`;
49
73
  export const FLOWS_PAGE_HTML = `<section data-reticle-page-panel="flows" class="reticle-page-panel reticle-flows-view" role="region" aria-label="Saved Flows" hidden>
50
74
  <div class="reticle-page-heading"><span class="reticle-page-titles"><strong>Saved flows</strong><span class="reticle-page-subtitle">Journeys your agent verified, replayable here</span></span><button type="button" data-reticle-page-close aria-label="Close Saved Flows" title="Close">×</button></div>
51
75
  <div data-reticle-all-flows class="reticle-all-flows"></div>
52
76
  </section>`;
53
77
  const NAV_ICON_SIZE = PRESENTER_ICON_SIZE.NAV;
78
+ export const NAV_LABEL = {
79
+ LOG: 'Log',
80
+ FLOWS: 'Flows',
81
+ NOTES: 'Notes',
82
+ IMPACT: 'Impact',
83
+ HARNESS: 'Harness',
84
+ SETTINGS: 'Settings',
85
+ HIDE: 'Hide',
86
+ };
87
+ const IMPACT_ARIA = { SEEN: 'Open Impact', NEW: 'Open Impact, new results' };
88
+ export const navLabelHtml = (text) => `<span class="reticle-nav-label">${text}</span>`;
54
89
  export const CHAT_VIEWS_NAV_HTML = `<nav class="reticle-chat-nav" aria-label="Reticle views">
55
- <button type="button" ${CHAT_TOGGLE_ATTR} data-reticle-chat-view-btn="activity" aria-label="Agent log" title="Agent log" aria-current="page">${hiIconHtml(PresenterIcon.MESSAGE, NAV_ICON_SIZE)}</button>
56
- <button type="button" data-reticle-chat-view-btn="flows" aria-label="Saved flows" title="Saved flows">${hiIconHtml(PresenterIcon.LAYOUT, NAV_ICON_SIZE)}</button>
57
- <button type="button" data-reticle-chat-view-btn="annotations" aria-label="Notes" title="Notes">${hiIconHtml(PresenterIcon.NOTES, NAV_ICON_SIZE)}<span data-reticle-note-count class="reticle-nav-count" hidden></span></button>
58
- <button type="button" data-reticle-chat-impact class="reticle-chat-impact" aria-label="Open Impact" title="Impact">${hiIconHtml(PresenterIcon.CHART, NAV_ICON_SIZE)}<span data-reticle-chat-verdicts class="reticle-nav-count"></span></button>
90
+ <button type="button" ${CHAT_TOGGLE_ATTR} data-reticle-chat-view-btn="activity" aria-label="Agent log" title="Agent log" aria-current="page">${hiIconHtml(PresenterIcon.MESSAGE, NAV_ICON_SIZE)}${navLabelHtml(NAV_LABEL.LOG)}</button>
91
+ <button type="button" data-reticle-chat-view-btn="harness" aria-label="${HARNESS_ROW_TEXT.PAGE_TITLE}" title="${HARNESS_ROW_TEXT.PAGE_TITLE}" hidden>${hiIconHtml(PresenterIcon.PLAY, NAV_ICON_SIZE)}${navLabelHtml(NAV_LABEL.HARNESS)}</button>
92
+ <button type="button" data-reticle-chat-view-btn="flows" aria-label="Saved flows" title="Saved flows">${hiIconHtml(PresenterIcon.LAYOUT, NAV_ICON_SIZE)}${navLabelHtml(NAV_LABEL.FLOWS)}</button>
93
+ <button type="button" data-reticle-chat-view-btn="annotations" aria-label="Notes" title="Notes">${hiIconHtml(PresenterIcon.NOTES, NAV_ICON_SIZE)}<span class="reticle-nav-label">${NAV_LABEL.NOTES}<span data-reticle-note-count class="reticle-nav-count" hidden></span></span></button>
94
+ <button type="button" data-reticle-chat-impact class="reticle-chat-impact" aria-label="${IMPACT_ARIA.SEEN}" title="Impact">${hiIconHtml(PresenterIcon.CHART, NAV_ICON_SIZE)}${navLabelHtml(NAV_LABEL.IMPACT)}<span data-reticle-impact-dot class="reticle-nav-dot" aria-hidden="true" hidden></span></button>
59
95
  </nav>`;
60
96
  const ANNOTATION_ACTION_ICON_SIZE = PRESENTER_ICON_SIZE.TOOLBAR;
61
97
  const markersLabel = 'Show note markers on the page';
62
98
  const clearLabel = 'Clear notes from this page';
63
99
  const copyAllLabel = 'Copy all notes';
64
100
  const NOTES_TEXT = {
101
+ SUBTITLE: 'Feedback for your agent',
102
+ SUBTITLE_FULL: 'Pin feedback on the page for your agent',
65
103
  PAGE_EMPTY: 'No notes on this page',
66
104
  PAGE_HINT: 'Select Add notes, then click anything on the page to pin feedback for your agent.',
67
105
  HISTORY_EMPTY: 'No saved notes yet',
68
106
  HISTORY_HINT: 'Notes from every page you annotate are kept here.',
69
107
  };
108
+ export const HARNESS_PAGE_HTML = `<section data-reticle-page-panel="harness" class="reticle-page-panel reticle-harness-page" role="region" aria-label="${HARNESS_ROW_TEXT.PAGE_TITLE}" hidden>
109
+ <div class="reticle-page-heading"><span class="reticle-page-titles"><strong>${HARNESS_ROW_TEXT.PAGE_TITLE}</strong><span class="reticle-page-subtitle">${HARNESS_ROW_TEXT.PAGE_SUBTITLE}</span></span><button type="button" data-reticle-page-close aria-label="Close ${HARNESS_ROW_TEXT.PAGE_TITLE}" title="Close">×</button></div>
110
+ <div data-reticle-harness-page class="reticle-harness-body"></div>
111
+ ${PLAN_HTML}
112
+ <div ${HARNESS_LOG_ATTR} ${LAST_ACTOR_ATTR}="${LOG_ACTOR.HARNESS}" class="reticle-harness-log" aria-live="polite" aria-label="${HARNESS_ROW_TEXT.LOG_LABEL}"></div>
113
+ <p data-reticle-harness-page-meta class="reticle-foot-meta"></p>
114
+ </section>`;
70
115
  export const ANNOTATIONS_HTML = `<section data-reticle-page-panel="annotations" class="reticle-page-panel reticle-annotations-view" role="region" aria-label="Notes" hidden>
71
- <div class="reticle-page-heading"><span class="reticle-page-titles"><strong>Notes</strong><span class="reticle-page-subtitle">Pin feedback on the page for your agent</span></span><button type="button" ${ANNOTATE_BTN_ATTR} class="reticle-add-notes reticle-primary-btn" title="Add notes" aria-label="Add notes" aria-pressed="false" data-active="0"><span class="reticle-add-notes-icon">${hiIconHtml(PresenterIcon.ANNOTATE, PRESENTER_ICON_SIZE.TOOLBAR)}</span><span data-reticle-notes-toggle-label>Add notes</span></button><button type="button" data-reticle-page-close aria-label="Close Notes" title="Close">×</button></div>
116
+ <div class="reticle-page-heading"><span class="reticle-page-titles"><strong>Notes</strong><span class="reticle-page-subtitle" title="${NOTES_TEXT.SUBTITLE_FULL}">${NOTES_TEXT.SUBTITLE}</span></span><button type="button" ${ANNOTATE_BTN_ATTR} class="reticle-add-notes reticle-primary-btn" title="Add notes" aria-label="Add notes" aria-pressed="false" data-active="0"><span class="reticle-add-notes-icon">${hiIconHtml(PresenterIcon.ANNOTATE, PRESENTER_ICON_SIZE.TOOLBAR)}</span><span data-reticle-notes-toggle-label>Add notes</span></button><button type="button" data-reticle-page-close aria-label="Close Notes" title="Close">×</button></div>
72
117
  <div class="reticle-annotations-tabs" role="group" aria-label="Notes lists">
73
118
  <span class="reticle-segmented"><button type="button" data-reticle-annotation-tab="current" aria-pressed="true">This page</button><button type="button" data-reticle-annotation-tab="history" aria-pressed="false">History</button></span>
74
119
  <div data-reticle-current-annotation-actions class="reticle-annotation-actions" aria-label="Current page note actions">
@@ -80,28 +125,32 @@ export const ANNOTATIONS_HTML = `<section data-reticle-page-panel="annotations"
80
125
  <div data-reticle-annotation-list class="reticle-annotation-list"></div>
81
126
  </section>`;
82
127
  export const CHAT_VIEWS_CSS = `
83
- [data-reticle-chat-panel] .reticle-harness-spot{flex:none;border-bottom:1px solid var(--reticle-hud-border);padding:0 var(--reticle-hud-space-3);}
84
- [data-reticle-chat-panel] .reticle-harness-spot[hidden]{display:none;}
85
- [data-reticle-chat-panel] .reticle-harness-row{display:flex;align-items:center;gap:var(--reticle-hud-space-2);min-height:40px;}
86
- [data-reticle-chat-panel] .reticle-harness-copy{display:flex;flex:1;min-width:0;flex-direction:column;line-height:1.3;}
87
- [data-reticle-chat-panel] .reticle-harness-copy strong{font-size:var(--reticle-hud-size-sm);font-weight:600;color:var(--reticle-hud-text);}
88
- [data-reticle-chat-panel] .reticle-harness-copy span{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
89
- [data-reticle-chat-panel] .reticle-harness-switch{flex:none;width:32px;height:20px;border:0;border-radius:20px;padding:2px;cursor:pointer;background:rgba(255,255,255,.2);}
90
- [data-reticle-chat-panel] .reticle-harness-switch::after{content:"";display:block;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s;}
91
- [data-reticle-chat-panel] .reticle-harness-switch[aria-checked="true"]{background:var(--reticle-accent);}
92
- [data-reticle-chat-panel] .reticle-harness-switch[aria-checked="true"]::after{transform:translateX(12px);}
93
- [data-reticle-chat-panel] .reticle-harness-switch:disabled{opacity:.45;cursor:not-allowed;}
94
- [data-reticle-chat-panel] .reticle-harness-link{flex:none;color:var(--reticle-hud-brand);font-size:var(--reticle-hud-size-xs);font-weight:600;text-decoration:none;white-space:nowrap;}
95
- [data-reticle-chat-panel] .reticle-harness-link:hover{text-decoration:underline;}
128
+ ${HARNESS_ROW_CSS}
129
+ ${HARNESS_ROW_CSS.replaceAll('[data-reticle-chat-panel]', '[data-reticle-page-panel="harness"]')}
130
+ [data-reticle-page-panel="harness"] .reticle-harness-body{flex:none;display:flex;flex-direction:column;gap:var(--reticle-hud-space-2);padding:var(--reticle-hud-space-3);}
131
+ /* The run as it happens, then its result: the Harness's rows only, scrolling under the controls. */
132
+ [data-reticle-page-panel="harness"] .reticle-harness-log{flex:1;min-height:0;overflow-y:auto;padding:0 var(--reticle-hud-space-3) var(--reticle-hud-space-2);}
133
+ [data-reticle-page-panel="harness"] .reticle-harness-log:empty::before{content:"${HARNESS_ROW_TEXT.LOG_EMPTY}";display:block;padding:var(--reticle-hud-space-2) 0;color:var(--reticle-hud-text-faint);font-size:11px;}
134
+ [data-reticle-page-panel="harness"] .reticle-foot-meta{flex:none;padding:0 var(--reticle-hud-space-3) var(--reticle-hud-space-3);}
135
+ [data-reticle-page-open="harness"] [data-reticle-page-panel="harness"]{display:flex;}
136
+ [data-reticle-page-open="harness"] [data-reticle-chat-panel]{display:none!important;}
96
137
  [data-reticle-hud] .reticle-chat-nav{display:contents;}
97
138
  [data-reticle-hud] .reticle-chat-nav button{position:relative;width:var(--reticle-hud-control-size);height:var(--reticle-hud-control-size);justify-self:center;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:var(--reticle-hud-radius-md);padding:0;background:transparent;color:var(--reticle-hud-text-muted);cursor:pointer;}
98
139
  [data-reticle-hud] .reticle-chat-nav button:hover{background:var(--reticle-hud-hover);color:var(--reticle-hud-text);}
99
- [data-reticle-hud] .reticle-chat-nav button[data-capture="1"]{color:var(--reticle-accent);box-shadow:inset 0 -2px var(--reticle-accent);}
100
- [data-reticle-hud] .reticle-chat-nav button[aria-current="page"],[data-reticle-hud] .reticle-chat-nav button[data-active="1"]{background:var(--reticle-hud-inset);color:var(--reticle-hud-text);}
140
+ [data-reticle-hud] .reticle-chat-nav button[aria-current="page"],[data-reticle-hud] .reticle-chat-nav button[data-active="1"]{background:transparent;color:var(--reticle-hud-text);}
141
+ /* The page you are on: a short straight bar under its name, nothing louder. */
142
+ [data-reticle-hud] .reticle-toolbar-chrome :is(.reticle-chat-nav button[aria-current="page"],.reticle-chat-nav button[data-active="1"],.reticle-tb-btn[data-active="1"])::after{content:"";position:absolute;left:50%;bottom:1px;width:16px;height:2px;border-radius:1px;transform:translateX(-50%);background:currentColor;}
101
143
  [data-reticle-hud] .reticle-chat-nav .reticle-hi-icon{opacity:.92;}
144
+ [data-reticle-hud] .reticle-chat-nav button[hidden]{display:none;}
145
+ /* An icon with its name under it, small: every page says what it is without a hover. */
146
+ [data-reticle-hud] .reticle-toolbar-chrome .reticle-chat-nav button{flex-direction:column;gap:2px;width:100%;height:auto;min-height:42px;padding:4px 0 3px;}
147
+ [data-reticle-hud] .reticle-nav-label{display:block;font-size:9px;line-height:1;font-weight:500;letter-spacing:.01em;color:inherit;white-space:nowrap;}
102
148
  [data-reticle-hud] .reticle-chat-nav .reticle-nav-count:empty{display:none;}
103
- [data-reticle-hud] .reticle-chat-nav .reticle-nav-count:not(:empty){position:absolute;top:0;right:0;min-width:13px;height:13px;display:inline-flex;align-items:center;justify-content:center;padding:0 3px;border-radius:99px;background:var(--reticle-accent);color:#fff;font-size:8px;font-weight:700;}
149
+ [data-reticle-hud] .reticle-chat-nav .reticle-nav-count:not(:empty){margin-left:3px;font-variant-numeric:tabular-nums;color:var(--reticle-hud-brand);font-weight:600;}
104
150
  [data-reticle-hud] .reticle-chat-nav .reticle-nav-count[hidden]{display:none;}
151
+ /* New results since Impact was last opened: a dot on the icon's corner, out of the flow, so no number can push the label. */
152
+ [data-reticle-hud] .reticle-chat-nav .reticle-nav-dot{position:absolute;top:4px;left:calc(50% + 5px);width:7px;height:7px;border-radius:50%;background:var(--reticle-hud-brand);box-shadow:0 0 0 2px var(--reticle-hud-surface);pointer-events:none;}
153
+ [data-reticle-hud] .reticle-chat-nav .reticle-nav-dot[hidden]{display:none;}
105
154
  [data-reticle-page-panel]{position:absolute;right:0;left:auto;bottom:calc(100% + 8px);z-index:6;box-sizing:border-box;width:var(--reticle-dock-w);max-width:min(var(--reticle-dock-w),calc(100vw - 16px));height:min(var(--reticle-chat-max-h,var(--reticle-chat-h)),calc(100vh - 120px));max-height:min(var(--reticle-chat-max-h,var(--reticle-chat-h)),calc(100vh - 120px));flex-direction:column;overflow:hidden;border-radius:var(--reticle-hud-radius-lg);${HUD_GLASS_PAINT}color:var(--reticle-hud-text);font-size:13px;line-height:1.5;pointer-events:auto;text-align:left;}
106
155
  [data-reticle-page-panel][hidden]{display:none!important;}
107
156
  [data-reticle-page-open="annotations"] [data-reticle-page-panel="annotations"],[data-reticle-page-open="flows"] [data-reticle-page-panel="flows"]{display:flex;}
@@ -110,7 +159,7 @@ export const CHAT_VIEWS_CSS = `
110
159
  .reticle-page-heading strong{font-size:12px;font-weight:600;}
111
160
  .reticle-page-heading button{width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--reticle-hud-text-muted);font:inherit;font-size:19px;line-height:1;cursor:pointer;}
112
161
  .reticle-page-heading button:hover{background:var(--reticle-hud-hover);color:var(--reticle-hud-text);}
113
- .reticle-page-subtitle{flex:none;padding:5px 12px;border-bottom:1px solid var(--reticle-hud-border);color:var(--reticle-hud-text-muted);font-size:10px;}
162
+ .reticle-page-subtitle{flex:none;padding:5px 12px;border-bottom:1px solid var(--reticle-hud-border);color:var(--reticle-hud-text-muted);font-size:11px;}
114
163
  .reticle-page-panel .reticle-all-flows,.reticle-page-panel .reticle-annotation-list{flex:1;min-height:0;overflow-y:auto;}
115
164
  [data-reticle-page-panel="annotations"] .reticle-annotations-tabs{flex:none;}
116
165
  [data-reticle-page-panel="annotations"] .reticle-annotation-list{flex:1;}
@@ -135,27 +184,59 @@ export const CHAT_VIEWS_CSS = `
135
184
  [data-reticle-page-panel="annotations"] .reticle-annotation-content{flex:1;min-width:0;}
136
185
  [data-reticle-page-panel="annotations"] .reticle-annotation-content strong{display:block;font-size:11px;font-weight:600;overflow-wrap:anywhere;}
137
186
  [data-reticle-page-panel="annotations"] .reticle-annotation-content span{display:block;margin-top:3px;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);overflow-wrap:anywhere;}
138
- [data-reticle-page-panel="annotations"] .reticle-annotation-item button{flex:none;border:1px solid var(--reticle-line);border-radius:6px;background:rgba(255,255,255,.04);color:var(--reticle-muted);font:inherit;font-size:10px;padding:4px 6px;cursor:pointer;}
187
+ [data-reticle-page-panel="annotations"] .reticle-annotation-item button{flex:none;border:1px solid var(--reticle-line);border-radius:6px;background:rgba(255,255,255,.04);color:var(--reticle-muted);font:inherit;font-size:11px;padding:4px 6px;cursor:pointer;}
139
188
  [data-reticle-page-panel="annotations"] .reticle-annotation-item button:hover{color:#fff;background:rgba(255,255,255,.1);}
140
- `;
189
+ ${AGENT_LINK_CSS}`;
190
+ const RUN_CONTROL = '[data-reticle-harness-page] :is([data-reticle-harness-run],[data-reticle-harness-stop])';
191
+ function readViewMemory() {
192
+ try {
193
+ const raw = JSON.parse(globalThis.sessionStorage.getItem(ReticleStorageKey.PRESENTER_VIEW) ?? '{}');
194
+ const r = (null !== raw && 'object' === typeof raw ? raw : {});
195
+ const view = PAGE_VIEWS.find((v) => v === r.view) ?? 'activity';
196
+ return { view, runFocused: true === r.runFocused };
197
+ }
198
+ catch {
199
+ return { view: 'activity', runFocused: false };
200
+ }
201
+ }
202
+ function rememberView(memory) {
203
+ try {
204
+ globalThis.sessionStorage.setItem(ReticleStorageKey.PRESENTER_VIEW, JSON.stringify(memory));
205
+ }
206
+ catch {
207
+ }
208
+ }
141
209
  export class ChatViews {
142
210
  #root;
143
- #view = 'activity';
211
+ #view = readViewMemory().view;
212
+ #refocusRun = readViewMemory().runFocused;
213
+ #runFocused = false;
144
214
  #annotationTab = 'current';
145
215
  #current = [];
146
216
  #history = readHistory();
147
217
  #account;
148
218
  #harness;
149
- #pendingHarness;
219
+ #gate;
150
220
  #verdicts = 0;
151
- #onHarness;
221
+ #impactOpen = false;
152
222
  #onImpact;
153
223
  #onOpenChat;
154
224
  #listeners;
155
- constructor(onHarness, onImpact, onOpenChat) {
156
- this.#onHarness = onHarness;
225
+ #drive;
226
+ #driving;
227
+ #projectId;
228
+ #personaPick = DEFAULT_PERSONA;
229
+ #customPersona = '';
230
+ #harnessHtml = '';
231
+ #harnessPageHtml = '';
232
+ #linking = false;
233
+ #agent = new AgentLinkView();
234
+ #startedAs;
235
+ #starting;
236
+ constructor(onImpact, onOpenChat, drive) {
157
237
  this.#onImpact = onImpact;
158
238
  this.#onOpenChat = onOpenChat;
239
+ this.#drive = drive;
159
240
  }
160
241
  mount(root) {
161
242
  this.#root = root;
@@ -169,20 +250,85 @@ export class ChatViews {
169
250
  root
170
251
  .querySelector('[data-reticle-chat-impact]')
171
252
  ?.addEventListener('click', () => this.#onImpact(), { signal });
172
- root.querySelector('[data-reticle-harness-spot]')?.addEventListener('click', (event) => {
253
+ const onHarnessClick = (event) => {
254
+ const target = event.target;
255
+ if (!(target instanceof Element))
256
+ return;
257
+ if (null !== target.closest(`[${HARNESS_OPEN_ATTR}]`)) {
258
+ this.#onOpenChat('harness');
259
+ this.open('harness');
260
+ return;
261
+ }
262
+ if (null !== target.closest(`[${HARNESS_SWITCH_ON_ATTR}]`)) {
263
+ this.#drive?.setHarness(true);
264
+ return;
265
+ }
266
+ const toggle = target.closest(`[${HARNESS_TOGGLE_ATTR}]`);
267
+ if (null !== toggle) {
268
+ const on = 'true' !== toggle.getAttribute('aria-checked');
269
+ toggle.setAttribute('aria-checked', String(on));
270
+ this.#drive?.setHarness(on);
271
+ return;
272
+ }
273
+ const linkButton = target.closest(`[${HARNESS_LINK_ATTR}],[${TEAM_SAVE_ATTR}]:not([${ACCOUNT_SIGNIN_ATTR}])`);
274
+ if (null !== linkButton) {
275
+ if (linkButton.disabled)
276
+ return;
277
+ this.#linking = true;
278
+ this.#drive?.link();
279
+ this.#paintHarness();
280
+ return;
281
+ }
282
+ const run = target.closest('[data-reticle-harness-run]');
283
+ if (null !== run) {
284
+ if (run.disabled)
285
+ return;
286
+ this.#startedAs = this.#personaPick;
287
+ this.#markStarting(true);
288
+ this.#drive?.run(personaText(this.#personaPick, this.#customPersona));
289
+ return;
290
+ }
291
+ if (null !== target.closest('[data-reticle-harness-stop]')) {
292
+ this.#drive?.stop();
293
+ return;
294
+ }
295
+ const copy = target.closest(`[${HARNESS_COPY_PROMPT_ATTR}]`);
296
+ if (null !== copy) {
297
+ const prompt = this.#gate?.prompt;
298
+ if (prompt !== undefined)
299
+ void navigator.clipboard
300
+ ?.writeText(prompt)
301
+ .then(() => (copy.textContent = HARNESS_ROW_TEXT.COPIED))
302
+ .catch(() => undefined);
303
+ }
304
+ };
305
+ root
306
+ .querySelector('[data-reticle-harness-spot]')
307
+ ?.addEventListener('click', onHarnessClick, { signal });
308
+ root
309
+ .querySelector('[data-reticle-harness-page]')
310
+ ?.addEventListener('click', onHarnessClick, { signal });
311
+ root
312
+ .querySelector(`[${TEAM_SLOT_ATTR}]`)
313
+ ?.addEventListener('click', onHarnessClick, { signal });
314
+ const spot = root.querySelector('[data-reticle-harness-page]');
315
+ spot?.addEventListener('change', (event) => {
173
316
  const target = event.target;
174
- if (!(target instanceof Element) ||
175
- null === target.closest('[data-reticle-harness-switch]'))
317
+ if (!(target instanceof HTMLSelectElement))
176
318
  return;
177
- const toggle = target.closest('[data-reticle-harness-switch]');
178
- if (null === toggle || toggle.disabled)
319
+ const pick = knownPick(target.value);
320
+ if (pick === undefined)
179
321
  return;
180
- const enabled = toggle.getAttribute('aria-checked') !== 'true';
181
- this.#pendingHarness = enabled;
182
- toggle.setAttribute('aria-checked', String(enabled));
183
- toggle.disabled = true;
184
- this.#onHarness(enabled);
322
+ this.#personaPick = pick;
323
+ savePick(this.#projectId, pick);
324
+ this.#paintHarness();
325
+ this.#root?.querySelector(`#${PERSONA_PICK_ID}`)?.focus();
185
326
  }, { signal });
327
+ spot?.addEventListener('input', (event) => {
328
+ if (event.target instanceof HTMLInputElement)
329
+ this.#customPersona = event.target.value;
330
+ }, { signal });
331
+ this.#agent.mount(root, signal);
186
332
  root.querySelectorAll('[data-reticle-annotation-tab]').forEach((button) => button.addEventListener('click', () => {
187
333
  this.#annotationTab =
188
334
  'history' === button.getAttribute('data-reticle-annotation-tab')
@@ -214,20 +360,36 @@ export class ChatViews {
214
360
  if (item !== undefined)
215
361
  this.#copy([item], 'pageUrl' in item ? item.pageUrl : location.href);
216
362
  }, { signal });
363
+ root.addEventListener('focusin', (event) => this.#noteRunFocus(event.target instanceof Element && event.target.matches(RUN_CONTROL)), { signal });
364
+ root.addEventListener('focusout', (event) => {
365
+ const next = event.relatedTarget;
366
+ this.#noteRunFocus(next instanceof Element && next.matches(RUN_CONTROL));
367
+ }, { signal });
217
368
  this.open(this.#view);
218
369
  this.#paintHarness();
219
370
  this.paintImpact(this.#verdicts);
220
371
  this.#paintAnnotations();
221
372
  }
222
373
  teardown() {
374
+ this.#markStarting(false);
223
375
  this.#listeners?.abort();
224
376
  this.#listeners = undefined;
225
377
  this.#root = undefined;
378
+ this.#harnessHtml = '';
379
+ this.#harnessPageHtml = '';
380
+ this.#agent.teardown();
381
+ }
382
+ #noteRunFocus(on) {
383
+ if (on === this.#runFocused)
384
+ return;
385
+ this.#runFocused = on;
386
+ rememberView({ view: this.#view, runFocused: on });
226
387
  }
227
388
  open(view) {
228
- if (view !== 'activity' && view !== 'flows' && view !== 'annotations')
389
+ if (view !== 'activity' && !PAGE_VIEWS.includes(view))
229
390
  return;
230
391
  this.#view = view;
392
+ rememberView({ view, runFocused: this.#runFocused });
231
393
  const root = this.#root;
232
394
  if (root === undefined)
233
395
  return;
@@ -257,24 +419,78 @@ export class ChatViews {
257
419
  panel.hidden = true;
258
420
  });
259
421
  this.#view = 'activity';
422
+ rememberView({ view: 'activity', runFocused: false });
260
423
  root.querySelectorAll('[data-reticle-chat-view-btn]').forEach((button) => {
261
424
  button.removeAttribute('aria-current');
262
425
  });
263
426
  }
427
+ setProjectId(projectId) {
428
+ this.#projectId = projectId;
429
+ this.#personaPick = readPick(projectId);
430
+ this.#paintHarness();
431
+ this.#paintImpactDot();
432
+ }
433
+ paintAgent(link) {
434
+ this.#agent.paint(link);
435
+ this.#paintMeta();
436
+ }
264
437
  paintAccount(account) {
265
438
  this.#account = account;
439
+ const nav = this.#root?.querySelector('[data-reticle-chat-view-btn="harness"]');
440
+ if (nav !== null && nav !== undefined)
441
+ nav.hidden = true !== account?.signedIn;
266
442
  this.#paintHarness();
267
443
  }
268
- paintHarness(config) {
444
+ paintDrive(drive) {
445
+ this.#driving = drive;
446
+ if (drive !== undefined)
447
+ this.#markStarting(false);
448
+ else if (this.#starting === undefined)
449
+ this.#startedAs = undefined;
450
+ this.#paintHarness();
451
+ }
452
+ #markStarting(on) {
453
+ if (this.#starting !== undefined)
454
+ clearTimeout(this.#starting);
455
+ this.#starting = on
456
+ ? setTimeout(() => {
457
+ this.#starting = undefined;
458
+ if (this.#driving === undefined)
459
+ this.#startedAs = undefined;
460
+ this.#paintHarness();
461
+ }, HARNESS_STARTING_MS)
462
+ : undefined;
463
+ if (on)
464
+ this.#paintHarness();
465
+ }
466
+ paintHarness(config, gate) {
467
+ if (config !== undefined)
468
+ this.#linking = false;
269
469
  this.#harness = config;
270
- this.#pendingHarness = undefined;
470
+ this.#gate = gate;
271
471
  this.#paintHarness();
272
472
  }
273
473
  paintImpact(verdicts) {
274
474
  this.#verdicts = verdicts;
275
- const el = this.#root?.querySelector('[data-reticle-chat-verdicts]');
276
- if (el !== null && el !== undefined)
277
- el.textContent = verdicts > 0 ? `· ${String(verdicts)}` : '';
475
+ if (this.#impactOpen)
476
+ saveImpactSeen(this.#projectId, verdicts);
477
+ this.#paintImpactDot();
478
+ }
479
+ setImpactOpen(open) {
480
+ this.#impactOpen = open;
481
+ if (open)
482
+ saveImpactSeen(this.#projectId, this.#verdicts);
483
+ this.#paintImpactDot();
484
+ }
485
+ #paintImpactDot() {
486
+ const button = this.#root?.querySelector('[data-reticle-chat-impact]');
487
+ const dot = button?.querySelector('[data-reticle-impact-dot]');
488
+ if (button === undefined || null === button || dot === undefined || null === dot)
489
+ return;
490
+ const seen = this.#impactOpen ? this.#verdicts : readImpactSeen(this.#projectId);
491
+ const fresh = seen !== undefined && this.#verdicts > seen;
492
+ dot.hidden = !fresh;
493
+ button.setAttribute('aria-label', fresh ? IMPACT_ARIA.NEW : IMPACT_ARIA.SEEN);
278
494
  }
279
495
  paintAnnotations(items) {
280
496
  this.#current = [...items];
@@ -294,34 +510,58 @@ export class ChatViews {
294
510
  this.#paintAnnotations();
295
511
  }
296
512
  #paintHarness() {
297
- const spot = this.#root?.querySelector('[data-reticle-harness-spot]');
298
- if (null === spot || spot === undefined)
513
+ const root = this.#root;
514
+ const spot = root?.querySelector('[data-reticle-harness-spot]');
515
+ if (root === undefined || null === spot || spot === undefined)
299
516
  return;
300
517
  const account = this.#account;
301
518
  const config = this.#harness;
302
- if (account === undefined || !account.signedIn) {
303
- spot.hidden = true;
304
- return;
305
- }
306
519
  spot.hidden = false;
307
- const base = platformBase(config, account);
308
- const setupUrl = `${base}${HARNESS_SETUP_PATH}`;
309
- const planUrl = `${base}${HARNESS_PLAN_PATH}`;
310
- if (config === undefined) {
311
- spot.innerHTML = `<div class="reticle-harness-row"><div class="reticle-harness-copy"><strong>Reticle Harness</strong><span>Link project to enable driving</span></div><button type="button" role="switch" data-reticle-harness-switch class="reticle-harness-switch" aria-label="Reticle Harness autonomous driving" aria-checked="false" aria-disabled="true" disabled></button><a class="reticle-harness-link" href="${setupUrl}" target="_blank" rel="noopener noreferrer">Set up ↗</a></div>`;
312
- }
313
- else if (!config.harnessEntitled) {
314
- spot.innerHTML = `<div class="reticle-harness-row"><div class="reticle-harness-copy"><strong>Reticle Harness</strong><span>Describe a user and Reticle drives the whole journey for you</span></div><a class="reticle-harness-link" href="${planUrl}" target="_blank" rel="noopener noreferrer">See plans ↗</a></div>`;
520
+ const base = platformBase(config, account ?? { signedIn: false });
521
+ const project = this.#projectId === undefined ? '' : `&project=${encodeURIComponent(this.#projectId)}`;
522
+ const state = {
523
+ account,
524
+ config,
525
+ gate: this.#gate,
526
+ drive: this.#driving,
527
+ canDrive: this.#drive !== undefined,
528
+ pick: this.#personaPick,
529
+ startedAs: this.#startedAs,
530
+ setupUrl: `${base}${HARNESS_SETUP_PATH}${project}${HARNESS_SETUP_HASH}`,
531
+ planUrl: `${base}${HARNESS_PLAN_PATH}`,
532
+ linking: this.#linking,
533
+ starting: this.#starting !== undefined && this.#driving === undefined,
534
+ };
535
+ const foot = harnessFootHtml(state);
536
+ if (foot !== this.#harnessHtml) {
537
+ morphHtml(spot, foot);
538
+ this.#harnessHtml = foot;
315
539
  }
316
- else if (config.credits !== undefined && config.credits.used >= config.credits.limit) {
317
- spot.innerHTML = `<div class="reticle-harness-row"><div class="reticle-harness-copy"><strong>Reticle Harness</strong><span>${creditsLeft(config.credits)}. They renew over 30 days.</span></div><a class="reticle-harness-link" href="${planUrl}" target="_blank" rel="noopener noreferrer">Get more with Pro ↗</a></div>`;
318
- }
319
- else if (false === config.providerReady) {
320
- spot.innerHTML = `<div class="reticle-harness-row"><div class="reticle-harness-copy"><strong>Reticle Harness</strong><span>Choose a model provider to start driving</span></div><a class="reticle-harness-link" href="${setupUrl}" target="_blank" rel="noopener noreferrer">Set up ↗</a></div>`;
321
- }
322
- else {
323
- spot.innerHTML = `<div class="reticle-harness-row"><div class="reticle-harness-copy"><strong>Reticle Harness</strong><span>${config.credits === undefined ? 'Autonomous checks' : creditsLeft(config.credits)}</span></div><button type="button" role="switch" data-reticle-harness-switch class="reticle-harness-switch" aria-label="Reticle Harness autonomous driving" aria-checked="${String(this.#pendingHarness ?? config.harnessEnabled)}" ${this.#pendingHarness === undefined ? '' : 'disabled'}></button></div>`;
540
+ if (account !== undefined && !account.signedIn && 'harness' === this.#view)
541
+ this.closePage();
542
+ const page = root.querySelector('[data-reticle-harness-page]');
543
+ if (page !== null) {
544
+ const body = harnessRowHtml(state);
545
+ if (body !== this.#harnessPageHtml) {
546
+ morphHtml(page, body);
547
+ this.#harnessPageHtml = body;
548
+ }
549
+ const control = this.#refocusRun ? page.querySelector(RUN_CONTROL) : null;
550
+ if (null !== control && 'harness' === this.#view) {
551
+ this.#refocusRun = false;
552
+ control.focus();
553
+ }
554
+ const custom = page.querySelector('[data-reticle-harness-persona]');
555
+ if (custom !== null && custom.value !== this.#customPersona)
556
+ custom.value = this.#customPersona;
324
557
  }
558
+ this.#paintMeta();
559
+ }
560
+ #paintMeta() {
561
+ const signedIn = true === this.#account?.signedIn;
562
+ const pageMeta = this.#root?.querySelector('[data-reticle-harness-page-meta]');
563
+ if (pageMeta !== null && pageMeta !== undefined)
564
+ pageMeta.textContent = signedIn ? creditsShort(this.#harness?.credits) : '';
325
565
  }
326
566
  #visibleAnnotations() {
327
567
  return 'history' === this.#annotationTab ? this.#history : this.#current;
@@ -18,6 +18,7 @@ export interface PresenterOptions {
18
18
  idleNoticeMs?: number;
19
19
  idleEndMs?: number;
20
20
  sessionId?: string;
21
+ projectId?: string;
21
22
  narrationDwellMs?: number;
22
23
  border?: BorderMode;
23
24
  logMax?: number;
@@ -43,6 +44,7 @@ export declare const ACT_STRIP: {
43
44
  readonly STOPPED_PREFIX: "stopped · ";
44
45
  readonly NOW: "now";
45
46
  readonly SINCE_LAST: " since last action";
47
+ readonly harnessTesting: (steps: number) => string;
46
48
  };
47
49
  export declare const IDLE_END_MS = 300000;
48
50
  export declare const IDLE_END_MIN_MS = 5000;
@@ -25,6 +25,7 @@ export const ACT_STRIP = {
25
25
  STOPPED_PREFIX: 'stopped · ',
26
26
  NOW: 'now',
27
27
  SINCE_LAST: ' since last action',
28
+ harnessTesting: (steps) => `Testing · Harness · ${String(steps)} step${1 === steps ? '' : 's'}`,
28
29
  };
29
30
  export const IDLE_END_MS = 300_000;
30
31
  export const IDLE_END_MIN_MS = 5_000;
@@ -6,7 +6,6 @@ export declare const CONTROLS_CSS: string;
6
6
  export declare const CONTROLS_STATUS_HTML: string;
7
7
  export declare const CONTROLS_BANNER_HTML = "<div data-reticle-banner class=\"reticle-banner\">Session ended</div>";
8
8
  export declare const CONTROLS_MARKS_HTML: string;
9
- export declare const CONTROLS_FLOWS_HTML: string;
10
9
  export declare const CONTROLS_FOOT_HTML: string;
11
10
  interface ControlPanelHost {
12
11
  emit: (kind: HumanControlKind, text?: string) => void;