@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
@@ -1,19 +1,38 @@
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_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
- const base = [config?.platformUrl, account.host].find((url) => url !== undefined && isSafeDashboardUrl(url));
23
+ const base = [consoleOrigin(config?.planUrl), config?.platformUrl, account.host].find((url) => url !== undefined && isSafeDashboardUrl(url));
15
24
  return esc((base ?? DEFAULT_PLATFORM_URL).replace(/\/+$/, ''));
16
25
  }
26
+ function consoleOrigin(planUrl) {
27
+ if (planUrl === undefined || !isSafeDashboardUrl(planUrl))
28
+ return undefined;
29
+ try {
30
+ return new URL(planUrl).origin;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
17
36
  function isHistoricalAnnotation(item) {
18
37
  if ('object' !== typeof item || null === item)
19
38
  return false;
@@ -37,6 +56,21 @@ function readHistory() {
37
56
  return [];
38
57
  }
39
58
  }
59
+ function readImpactSeen(projectId) {
60
+ try {
61
+ return Number(localStorage.getItem(`${IMPACT_SEEN_KEY_PREFIX}${projectId ?? ''}`) ?? 0) || 0;
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ function saveImpactSeen(projectId, verdicts) {
68
+ try {
69
+ localStorage.setItem(`${IMPACT_SEEN_KEY_PREFIX}${projectId ?? ''}`, String(verdicts));
70
+ }
71
+ catch {
72
+ }
73
+ }
40
74
  function saveHistory(items) {
41
75
  try {
42
76
  localStorage.setItem(HISTORY_KEY, JSON.stringify(items.slice(0, HISTORY_LIMIT)));
@@ -44,31 +78,52 @@ function saveHistory(items) {
44
78
  catch {
45
79
  }
46
80
  }
47
- export const CHAT_VIEWS_HTML = `
81
+ export const CHAT_VIEWS_HTML = `${AGENT_LINK_HTML}
48
82
  <div data-reticle-harness-spot class="reticle-harness-spot" hidden></div>`;
49
83
  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
84
  <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
85
  <div data-reticle-all-flows class="reticle-all-flows"></div>
52
86
  </section>`;
53
87
  const NAV_ICON_SIZE = PRESENTER_ICON_SIZE.NAV;
88
+ export const NAV_LABEL = {
89
+ LOG: 'Log',
90
+ FLOWS: 'Flows',
91
+ NOTES: 'Notes',
92
+ IMPACT: 'Impact',
93
+ HARNESS: 'Harness',
94
+ SETTINGS: 'Settings',
95
+ HIDE: 'Hide',
96
+ };
97
+ const IMPACT_ARIA = { SEEN: 'Open Impact', NEW: 'Open Impact, new results' };
98
+ export const navLabelHtml = (text) => `<span class="reticle-nav-label">${text}</span>`;
54
99
  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>
100
+ <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>
101
+ <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>
102
+ <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>
103
+ <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>
104
+ <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
105
  </nav>`;
60
106
  const ANNOTATION_ACTION_ICON_SIZE = PRESENTER_ICON_SIZE.TOOLBAR;
61
107
  const markersLabel = 'Show note markers on the page';
62
108
  const clearLabel = 'Clear notes from this page';
63
109
  const copyAllLabel = 'Copy all notes';
64
110
  const NOTES_TEXT = {
111
+ SUBTITLE: 'Feedback for your agent',
112
+ SUBTITLE_FULL: 'Pin feedback on the page for your agent',
65
113
  PAGE_EMPTY: 'No notes on this page',
66
114
  PAGE_HINT: 'Select Add notes, then click anything on the page to pin feedback for your agent.',
67
115
  HISTORY_EMPTY: 'No saved notes yet',
68
116
  HISTORY_HINT: 'Notes from every page you annotate are kept here.',
69
117
  };
118
+ 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>
119
+ <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>
120
+ <div data-reticle-harness-page class="reticle-harness-body"></div>
121
+ ${PLAN_HTML}
122
+ <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>
123
+ <p data-reticle-harness-page-meta class="reticle-foot-meta"></p>
124
+ </section>`;
70
125
  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>
126
+ <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
127
  <div class="reticle-annotations-tabs" role="group" aria-label="Notes lists">
73
128
  <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
129
  <div data-reticle-current-annotation-actions class="reticle-annotation-actions" aria-label="Current page note actions">
@@ -80,28 +135,32 @@ export const ANNOTATIONS_HTML = `<section data-reticle-page-panel="annotations"
80
135
  <div data-reticle-annotation-list class="reticle-annotation-list"></div>
81
136
  </section>`;
82
137
  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;}
138
+ ${HARNESS_ROW_CSS}
139
+ ${HARNESS_ROW_CSS.replaceAll('[data-reticle-chat-panel]', '[data-reticle-page-panel="harness"]')}
140
+ [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);}
141
+ /* The run as it happens, then its result: the Harness's rows only, scrolling under the controls. */
142
+ [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);}
143
+ [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;}
144
+ [data-reticle-page-panel="harness"] .reticle-foot-meta{flex:none;padding:0 var(--reticle-hud-space-3) var(--reticle-hud-space-3);}
145
+ [data-reticle-page-open="harness"] [data-reticle-page-panel="harness"]{display:flex;}
146
+ [data-reticle-page-open="harness"] [data-reticle-chat-panel]{display:none!important;}
96
147
  [data-reticle-hud] .reticle-chat-nav{display:contents;}
97
148
  [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
149
  [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);}
150
+ [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);}
151
+ /* The page you are on: a short straight bar under its name, nothing louder. */
152
+ [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
153
  [data-reticle-hud] .reticle-chat-nav .reticle-hi-icon{opacity:.92;}
154
+ [data-reticle-hud] .reticle-chat-nav button[hidden]{display:none;}
155
+ /* An icon with its name under it, small: every page says what it is without a hover. */
156
+ [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;}
157
+ [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
158
  [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;}
159
+ [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
160
  [data-reticle-hud] .reticle-chat-nav .reticle-nav-count[hidden]{display:none;}
161
+ /* 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. */
162
+ [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;}
163
+ [data-reticle-hud] .reticle-chat-nav .reticle-nav-dot[hidden]{display:none;}
105
164
  [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
165
  [data-reticle-page-panel][hidden]{display:none!important;}
107
166
  [data-reticle-page-open="annotations"] [data-reticle-page-panel="annotations"],[data-reticle-page-open="flows"] [data-reticle-page-panel="flows"]{display:flex;}
@@ -110,7 +169,7 @@ export const CHAT_VIEWS_CSS = `
110
169
  .reticle-page-heading strong{font-size:12px;font-weight:600;}
111
170
  .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
171
  .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;}
172
+ .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
173
  .reticle-page-panel .reticle-all-flows,.reticle-page-panel .reticle-annotation-list{flex:1;min-height:0;overflow-y:auto;}
115
174
  [data-reticle-page-panel="annotations"] .reticle-annotations-tabs{flex:none;}
116
175
  [data-reticle-page-panel="annotations"] .reticle-annotation-list{flex:1;}
@@ -135,27 +194,62 @@ export const CHAT_VIEWS_CSS = `
135
194
  [data-reticle-page-panel="annotations"] .reticle-annotation-content{flex:1;min-width:0;}
136
195
  [data-reticle-page-panel="annotations"] .reticle-annotation-content strong{display:block;font-size:11px;font-weight:600;overflow-wrap:anywhere;}
137
196
  [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;}
197
+ [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
198
  [data-reticle-page-panel="annotations"] .reticle-annotation-item button:hover{color:#fff;background:rgba(255,255,255,.1);}
140
- `;
199
+ ${AGENT_LINK_CSS}`;
200
+ const RUN_CONTROL = '[data-reticle-harness-page] :is([data-reticle-harness-run],[data-reticle-harness-stop])';
201
+ function readViewMemory() {
202
+ try {
203
+ const raw = JSON.parse(globalThis.sessionStorage.getItem(ReticleStorageKey.PRESENTER_VIEW) ?? '{}');
204
+ const r = (null !== raw && 'object' === typeof raw ? raw : {});
205
+ const view = PAGE_VIEWS.find((v) => v === r.view) ?? 'activity';
206
+ return { view, runFocused: true === r.runFocused };
207
+ }
208
+ catch {
209
+ return { view: 'activity', runFocused: false };
210
+ }
211
+ }
212
+ function rememberView(memory) {
213
+ try {
214
+ globalThis.sessionStorage.setItem(ReticleStorageKey.PRESENTER_VIEW, JSON.stringify(memory));
215
+ }
216
+ catch {
217
+ }
218
+ }
141
219
  export class ChatViews {
142
220
  #root;
143
- #view = 'activity';
221
+ #view = readViewMemory().view;
222
+ #refocusRun = readViewMemory().runFocused;
223
+ #runFocused = false;
144
224
  #annotationTab = 'current';
145
225
  #current = [];
146
226
  #history = readHistory();
147
227
  #account;
148
228
  #harness;
149
- #pendingHarness;
150
229
  #verdicts = 0;
151
- #onHarness;
230
+ #impactOpen = false;
152
231
  #onImpact;
153
232
  #onOpenChat;
154
233
  #listeners;
155
- constructor(onHarness, onImpact, onOpenChat) {
156
- this.#onHarness = onHarness;
234
+ #drive;
235
+ #driving;
236
+ #projectId;
237
+ #personaPick = DEFAULT_PERSONA;
238
+ #customPersona = '';
239
+ #harnessHtml = '';
240
+ #harnessPageHtml = '';
241
+ #linking = false;
242
+ #linkTimer;
243
+ #agent = new AgentLinkView();
244
+ #startedAs;
245
+ #last;
246
+ #starting;
247
+ #lastSeq;
248
+ #pressedAfter;
249
+ constructor(onImpact, onOpenChat, drive) {
157
250
  this.#onImpact = onImpact;
158
251
  this.#onOpenChat = onOpenChat;
252
+ this.#drive = drive;
159
253
  }
160
254
  mount(root) {
161
255
  this.#root = root;
@@ -169,20 +263,84 @@ export class ChatViews {
169
263
  root
170
264
  .querySelector('[data-reticle-chat-impact]')
171
265
  ?.addEventListener('click', () => this.#onImpact(), { signal });
172
- root.querySelector('[data-reticle-harness-spot]')?.addEventListener('click', (event) => {
266
+ const onHarnessClick = (event) => {
267
+ const target = event.target;
268
+ if (!(target instanceof Element))
269
+ return;
270
+ if (null !== target.closest(`[${HARNESS_OPEN_ATTR}]`)) {
271
+ this.#onOpenChat('harness');
272
+ this.open('harness');
273
+ return;
274
+ }
275
+ if (null !== target.closest(`[${HARNESS_SWITCH_ON_ATTR}]`)) {
276
+ this.#drive?.setHarness(true);
277
+ return;
278
+ }
279
+ const toggle = target.closest(`[${HARNESS_TOGGLE_ATTR}]`);
280
+ if (null !== toggle) {
281
+ const on = 'true' !== toggle.getAttribute('aria-checked');
282
+ toggle.setAttribute('aria-checked', String(on));
283
+ this.#drive?.setHarness(on);
284
+ return;
285
+ }
286
+ const linkButton = target.closest(`[${HARNESS_LINK_ATTR}],[${TEAM_SAVE_ATTR}]:not([${ACCOUNT_SIGNIN_ATTR}])`);
287
+ if (null !== linkButton) {
288
+ if (linkButton.disabled)
289
+ return;
290
+ this.#linking = true;
291
+ if (this.#linkTimer !== undefined)
292
+ clearTimeout(this.#linkTimer);
293
+ this.#linkTimer = setTimeout(() => {
294
+ this.#linkTimer = undefined;
295
+ this.#linking = false;
296
+ this.#paintHarness();
297
+ }, HARNESS_STARTING_MS);
298
+ this.#drive?.link();
299
+ this.#paintHarness();
300
+ return;
301
+ }
302
+ const run = target.closest('[data-reticle-harness-run]');
303
+ if (null !== run) {
304
+ if (run.disabled)
305
+ return;
306
+ this.#startedAs = this.#personaPick;
307
+ this.#pressedAfter = this.#lastSeq;
308
+ this.#markStarting(true);
309
+ this.#drive?.run(personaText(this.#personaPick, this.#customPersona));
310
+ return;
311
+ }
312
+ if (null !== target.closest('[data-reticle-harness-stop]')) {
313
+ this.#drive?.stop();
314
+ return;
315
+ }
316
+ };
317
+ root
318
+ .querySelector('[data-reticle-harness-spot]')
319
+ ?.addEventListener('click', onHarnessClick, { signal });
320
+ root
321
+ .querySelector('[data-reticle-harness-page]')
322
+ ?.addEventListener('click', onHarnessClick, { signal });
323
+ root
324
+ .querySelector(`[${TEAM_SLOT_ATTR}]`)
325
+ ?.addEventListener('click', onHarnessClick, { signal });
326
+ const spot = root.querySelector('[data-reticle-harness-page]');
327
+ spot?.addEventListener('change', (event) => {
173
328
  const target = event.target;
174
- if (!(target instanceof Element) ||
175
- null === target.closest('[data-reticle-harness-switch]'))
329
+ if (!(target instanceof HTMLSelectElement))
176
330
  return;
177
- const toggle = target.closest('[data-reticle-harness-switch]');
178
- if (null === toggle || toggle.disabled)
331
+ const pick = knownPick(target.value);
332
+ if (pick === undefined)
179
333
  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);
334
+ this.#personaPick = pick;
335
+ savePick(this.#projectId, pick);
336
+ this.#paintHarness();
337
+ this.#root?.querySelector(`#${PERSONA_PICK_ID}`)?.focus();
338
+ }, { signal });
339
+ spot?.addEventListener('input', (event) => {
340
+ if (event.target instanceof HTMLInputElement)
341
+ this.#customPersona = event.target.value;
185
342
  }, { signal });
343
+ this.#agent.mount(root, signal);
186
344
  root.querySelectorAll('[data-reticle-annotation-tab]').forEach((button) => button.addEventListener('click', () => {
187
345
  this.#annotationTab =
188
346
  'history' === button.getAttribute('data-reticle-annotation-tab')
@@ -214,20 +372,36 @@ export class ChatViews {
214
372
  if (item !== undefined)
215
373
  this.#copy([item], 'pageUrl' in item ? item.pageUrl : location.href);
216
374
  }, { signal });
375
+ root.addEventListener('focusin', (event) => this.#noteRunFocus(event.target instanceof Element && event.target.matches(RUN_CONTROL)), { signal });
376
+ root.addEventListener('focusout', (event) => {
377
+ const next = event.relatedTarget;
378
+ this.#noteRunFocus(next instanceof Element && next.matches(RUN_CONTROL));
379
+ }, { signal });
217
380
  this.open(this.#view);
218
381
  this.#paintHarness();
219
382
  this.paintImpact(this.#verdicts);
220
383
  this.#paintAnnotations();
221
384
  }
222
385
  teardown() {
386
+ this.#markStarting(false);
223
387
  this.#listeners?.abort();
224
388
  this.#listeners = undefined;
225
389
  this.#root = undefined;
390
+ this.#harnessHtml = '';
391
+ this.#harnessPageHtml = '';
392
+ this.#agent.teardown();
393
+ }
394
+ #noteRunFocus(on) {
395
+ if (on === this.#runFocused)
396
+ return;
397
+ this.#runFocused = on;
398
+ rememberView({ view: this.#view, runFocused: on });
226
399
  }
227
400
  open(view) {
228
- if (view !== 'activity' && view !== 'flows' && view !== 'annotations')
401
+ if (view !== 'activity' && !PAGE_VIEWS.includes(view))
229
402
  return;
230
403
  this.#view = view;
404
+ rememberView({ view, runFocused: this.#runFocused });
231
405
  const root = this.#root;
232
406
  if (root === undefined)
233
407
  return;
@@ -257,24 +431,91 @@ export class ChatViews {
257
431
  panel.hidden = true;
258
432
  });
259
433
  this.#view = 'activity';
434
+ rememberView({ view: 'activity', runFocused: false });
260
435
  root.querySelectorAll('[data-reticle-chat-view-btn]').forEach((button) => {
261
436
  button.removeAttribute('aria-current');
262
437
  });
263
438
  }
439
+ setProjectId(projectId) {
440
+ this.#projectId = projectId;
441
+ this.#personaPick = readPick(projectId);
442
+ this.#paintHarness();
443
+ this.#paintImpactDot();
444
+ }
445
+ paintAgent(link) {
446
+ this.#agent.paint(link);
447
+ this.#paintMeta();
448
+ }
264
449
  paintAccount(account) {
265
450
  this.#account = account;
451
+ const nav = this.#root?.querySelector('[data-reticle-chat-view-btn="harness"]');
452
+ if (nav !== null && nav !== undefined)
453
+ nav.hidden = true !== account?.signedIn;
454
+ this.#paintHarness();
455
+ }
456
+ paintDrive(drive, last) {
457
+ this.#driving = drive;
458
+ const runUrl = last?.runUrl !== undefined && isSafeDashboardUrl(last.runUrl) ? esc(last.runUrl) : undefined;
459
+ this.#last =
460
+ last === undefined
461
+ ? undefined
462
+ : {
463
+ ...(runUrl === undefined ? {} : { runUrl }),
464
+ ...(last.said === undefined ? {} : { said: last.said }),
465
+ };
466
+ this.#lastSeq = last?.seq;
467
+ const answered = last?.seq !== undefined && last.seq !== this.#pressedAfter && this.#starting !== undefined;
468
+ if (drive !== undefined || answered)
469
+ this.#markStarting(false);
470
+ if (drive === undefined && this.#starting === undefined)
471
+ this.#startedAs = undefined;
266
472
  this.#paintHarness();
267
473
  }
474
+ #markStarting(on) {
475
+ if (this.#starting !== undefined)
476
+ clearTimeout(this.#starting);
477
+ this.#starting = on
478
+ ? setTimeout(() => {
479
+ this.#starting = undefined;
480
+ if (this.#driving === undefined)
481
+ this.#startedAs = undefined;
482
+ this.#paintHarness();
483
+ }, HARNESS_STARTING_MS)
484
+ : undefined;
485
+ if (on)
486
+ this.#paintHarness();
487
+ }
268
488
  paintHarness(config) {
489
+ if (config !== undefined) {
490
+ this.#linking = false;
491
+ if (this.#linkTimer !== undefined)
492
+ clearTimeout(this.#linkTimer);
493
+ this.#linkTimer = undefined;
494
+ }
269
495
  this.#harness = config;
270
- this.#pendingHarness = undefined;
271
496
  this.#paintHarness();
272
497
  }
273
498
  paintImpact(verdicts) {
274
499
  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)}` : '';
500
+ if (this.#impactOpen)
501
+ saveImpactSeen(this.#projectId, verdicts);
502
+ this.#paintImpactDot();
503
+ }
504
+ setImpactOpen(open) {
505
+ this.#impactOpen = open;
506
+ if (open)
507
+ saveImpactSeen(this.#projectId, this.#verdicts);
508
+ this.#paintImpactDot();
509
+ }
510
+ #paintImpactDot() {
511
+ const button = this.#root?.querySelector('[data-reticle-chat-impact]');
512
+ const dot = button?.querySelector('[data-reticle-impact-dot]');
513
+ if (button === undefined || null === button || dot === undefined || null === dot)
514
+ return;
515
+ const seen = this.#impactOpen ? this.#verdicts : readImpactSeen(this.#projectId);
516
+ const fresh = seen !== undefined && this.#verdicts > seen;
517
+ dot.hidden = !fresh;
518
+ button.setAttribute('aria-label', fresh ? IMPACT_ARIA.NEW : IMPACT_ARIA.SEEN);
278
519
  }
279
520
  paintAnnotations(items) {
280
521
  this.#current = [...items];
@@ -294,34 +535,60 @@ export class ChatViews {
294
535
  this.#paintAnnotations();
295
536
  }
296
537
  #paintHarness() {
297
- const spot = this.#root?.querySelector('[data-reticle-harness-spot]');
298
- if (null === spot || spot === undefined)
538
+ const root = this.#root;
539
+ const spot = root?.querySelector('[data-reticle-harness-spot]');
540
+ if (root === undefined || null === spot || spot === undefined)
299
541
  return;
300
542
  const account = this.#account;
301
543
  const config = this.#harness;
302
- if (account === undefined || !account.signedIn) {
303
- spot.hidden = true;
304
- return;
305
- }
306
544
  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>`;
545
+ const base = platformBase(config, account ?? { signedIn: false });
546
+ const project = this.#projectId === undefined ? '' : `&project=${encodeURIComponent(this.#projectId)}`;
547
+ const state = {
548
+ account,
549
+ config,
550
+ drive: this.#driving,
551
+ canDrive: this.#drive !== undefined,
552
+ pick: this.#personaPick,
553
+ startedAs: this.#startedAs,
554
+ setupUrl: `${base}${HARNESS_SETUP_PATH}${project}${HARNESS_SETUP_HASH}`,
555
+ planUrl: config?.planUrl !== undefined && isSafeDashboardUrl(config.planUrl)
556
+ ? esc(config.planUrl)
557
+ : `${base}${HARNESS_PLAN_PATH}`,
558
+ linking: this.#linking,
559
+ starting: this.#starting !== undefined && this.#driving === undefined,
560
+ ...(this.#last === undefined ? {} : { last: this.#last }),
561
+ };
562
+ const foot = harnessFootHtml(state);
563
+ if (foot !== this.#harnessHtml) {
564
+ morphHtml(spot, foot);
565
+ this.#harnessHtml = foot;
315
566
  }
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>`;
567
+ if (account !== undefined && !account.signedIn && 'harness' === this.#view)
568
+ this.closePage();
569
+ const page = root.querySelector('[data-reticle-harness-page]');
570
+ if (page !== null) {
571
+ const body = harnessRowHtml(state);
572
+ if (body !== this.#harnessPageHtml) {
573
+ morphHtml(page, body);
574
+ this.#harnessPageHtml = body;
575
+ }
576
+ const control = this.#refocusRun ? page.querySelector(RUN_CONTROL) : null;
577
+ if (null !== control && 'harness' === this.#view) {
578
+ this.#refocusRun = false;
579
+ control.focus();
580
+ }
581
+ const custom = page.querySelector('[data-reticle-harness-persona]');
582
+ if (custom !== null && custom.value !== this.#customPersona)
583
+ custom.value = this.#customPersona;
324
584
  }
585
+ this.#paintMeta();
586
+ }
587
+ #paintMeta() {
588
+ const signedIn = true === this.#account?.signedIn;
589
+ const pageMeta = this.#root?.querySelector('[data-reticle-harness-page-meta]');
590
+ if (pageMeta !== null && pageMeta !== undefined)
591
+ pageMeta.textContent = signedIn ? creditsShort(this.#harness?.credits) : '';
325
592
  }
326
593
  #visibleAnnotations() {
327
594
  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;
@@ -84,6 +86,7 @@ export declare const SETTINGS_CLOSE_ATTR = "data-reticle-settings-close";
84
86
  export declare const SETTING_KEY_ATTR = "data-reticle-setting";
85
87
  export declare const SETTINGS_STORAGE_KEY: "reticle-presenter-settings";
86
88
  export declare const MINIMISED_STORAGE_KEY: "reticle-presenter-minimised";
89
+ export declare const OPEN_STORAGE_KEY: "reticle-presenter-open";
87
90
  export declare const AMBIENT_GLOW_ATTR = "data-reticle-ambient-glow";
88
91
  export declare const LIVENESS_ATTR = "data-reticle-live";
89
92
  export declare const BLOCK_ATTR = "data-reticle-block";
@@ -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;
@@ -63,6 +64,7 @@ export const SETTINGS_CLOSE_ATTR = 'data-reticle-settings-close';
63
64
  export const SETTING_KEY_ATTR = 'data-reticle-setting';
64
65
  export const SETTINGS_STORAGE_KEY = ReticleStorageKey.PRESENTER_SETTINGS;
65
66
  export const MINIMISED_STORAGE_KEY = ReticleStorageKey.PRESENTER_MINIMISED;
67
+ export const OPEN_STORAGE_KEY = ReticleStorageKey.PRESENTER_OPEN;
66
68
  export const AMBIENT_GLOW_ATTR = 'data-reticle-ambient-glow';
67
69
  export const LIVENESS_ATTR = 'data-reticle-live';
68
70
  export const BLOCK_ATTR = 'data-reticle-block';
@@ -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;