@reticlehq/browser 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/actions/action-report.d.ts +54 -0
  2. package/dist/actions/action-report.js +60 -0
  3. package/dist/actions/actions-dom.d.ts +1 -0
  4. package/dist/actions/actions-dom.js +56 -43
  5. package/dist/actions/actions-press.d.ts +11 -4
  6. package/dist/actions/actions-press.js +132 -17
  7. package/dist/actions/actions.d.ts +1 -35
  8. package/dist/actions/actions.js +32 -65
  9. package/dist/actions/appeared-text.js +3 -0
  10. package/dist/actions/danger-context.d.ts +2 -0
  11. package/dist/actions/danger-context.js +29 -3
  12. package/dist/actions/value-input.d.ts +1 -0
  13. package/dist/actions/value-input.js +11 -0
  14. package/dist/commands/commands.js +4 -0
  15. package/dist/connect-options.d.ts +1 -0
  16. package/dist/dom/a11y.js +264 -14
  17. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  18. package/dist/dom/addressing/coverage-markers.js +13 -0
  19. package/dist/dom/addressing/refs.js +2 -1
  20. package/dist/dom/addressing/testid-attr.d.ts +4 -0
  21. package/dist/dom/addressing/testid-attr.js +14 -0
  22. package/dist/dom/dom-ignore.d.ts +2 -0
  23. package/dist/dom/dom-ignore.js +15 -2
  24. package/dist/dom/query.d.ts +2 -1
  25. package/dist/dom/query.js +116 -27
  26. package/dist/dom/render-page.d.ts +12 -0
  27. package/dist/dom/render-page.js +135 -0
  28. package/dist/dom/snapshot.js +56 -7
  29. package/dist/observers/departure.js +51 -9
  30. package/dist/observers/dom.js +35 -18
  31. package/dist/observers/field.js +5 -21
  32. package/dist/observers/health.js +63 -2
  33. package/dist/observers/network-stream.d.ts +1 -1
  34. package/dist/observers/network-stream.js +3 -3
  35. package/dist/observers/network.js +57 -8
  36. package/dist/observers/perf.js +53 -1
  37. package/dist/observers/route.js +6 -5
  38. package/dist/observers/storage.js +6 -1
  39. package/dist/presenter/carousel/carousel.d.ts +5 -2
  40. package/dist/presenter/carousel/carousel.js +30 -8
  41. package/dist/presenter/carousel/panel-slides.d.ts +3 -4
  42. package/dist/presenter/carousel/panel-slides.js +48 -9
  43. package/dist/presenter/carousel/talk-card.d.ts +1 -1
  44. package/dist/presenter/chrome/log-kinds.d.ts +5 -0
  45. package/dist/presenter/chrome/log-kinds.js +1 -0
  46. package/dist/presenter/chrome/log-memory.d.ts +13 -0
  47. package/dist/presenter/chrome/log-memory.js +39 -0
  48. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
  49. package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
  50. package/dist/presenter/chrome/presenter-log.d.ts +12 -4
  51. package/dist/presenter/chrome/presenter-log.js +100 -10
  52. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  53. package/dist/presenter/chrome/presenter-morph.js +34 -0
  54. package/dist/presenter/chrome/presenter-report-copy.d.ts +28 -5
  55. package/dist/presenter/chrome/presenter-report-copy.js +40 -5
  56. package/dist/presenter/hud-telemetry.js +83 -3
  57. package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
  58. package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
  59. package/dist/presenter/icons/presenter-icons.d.ts +2 -0
  60. package/dist/presenter/icons/presenter-icons.js +2 -0
  61. package/dist/presenter/presenter-account-styles.js +3 -3
  62. package/dist/presenter/presenter-account.d.ts +31 -8
  63. package/dist/presenter/presenter-account.js +105 -15
  64. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  65. package/dist/presenter/presenter-agent-link.js +62 -0
  66. package/dist/presenter/presenter-chat-views.d.ts +43 -0
  67. package/dist/presenter/presenter-chat-views.js +622 -0
  68. package/dist/presenter/presenter-config.d.ts +5 -2
  69. package/dist/presenter/presenter-config.js +5 -3
  70. package/dist/presenter/presenter-controls.d.ts +3 -2
  71. package/dist/presenter/presenter-controls.js +163 -61
  72. package/dist/presenter/presenter-design-tokens.d.ts +1 -0
  73. package/dist/presenter/presenter-design-tokens.js +34 -0
  74. package/dist/presenter/presenter-dock-layout.js +1 -1
  75. package/dist/presenter/presenter-frame.d.ts +12 -0
  76. package/dist/presenter/presenter-frame.js +187 -0
  77. package/dist/presenter/presenter-harness-row.d.ts +67 -0
  78. package/dist/presenter/presenter-harness-row.js +209 -0
  79. package/dist/presenter/presenter-personas.d.ts +35 -0
  80. package/dist/presenter/presenter-personas.js +95 -0
  81. package/dist/presenter/presenter-plan.d.ts +10 -0
  82. package/dist/presenter/presenter-plan.js +186 -0
  83. package/dist/presenter/presenter-report-styles.js +46 -21
  84. package/dist/presenter/presenter-report.d.ts +2 -1
  85. package/dist/presenter/presenter-report.js +123 -20
  86. package/dist/presenter/presenter-settings-styles.js +18 -4
  87. package/dist/presenter/presenter-settings.d.ts +8 -2
  88. package/dist/presenter/presenter-settings.js +134 -33
  89. package/dist/presenter/presenter-shell-styles.js +48 -42
  90. package/dist/presenter/presenter-shell.d.ts +17 -4
  91. package/dist/presenter/presenter-shell.js +139 -79
  92. package/dist/presenter/presenter-styles.js +9 -3
  93. package/dist/presenter/presenter-workspace.js +10 -3
  94. package/dist/presenter/presenter.d.ts +6 -4
  95. package/dist/presenter/presenter.js +171 -22
  96. package/dist/presenter/tour/tour.d.ts +4 -1
  97. package/dist/presenter/tour/tour.js +6 -4
  98. package/dist/recorder/recorder.js +2 -2
  99. package/dist/registry/auto-testids.js +3 -4
  100. package/dist/registry/declared-platform.js +4 -0
  101. package/dist/reticle-inject.js +938 -494
  102. package/dist/reticle.d.ts +10 -1
  103. package/dist/reticle.js +109 -11
  104. package/dist/review/annotator-styles.d.ts +1 -1
  105. package/dist/review/annotator.d.ts +7 -0
  106. package/dist/review/annotator.js +20 -2
  107. package/dist/review/mark-anchor.js +2 -2
  108. package/dist/session-continuity.js +2 -1
  109. package/dist/storage-keys.d.ts +15 -0
  110. package/dist/storage-keys.js +21 -0
  111. package/dist/transport/transport.js +5 -3
  112. package/dist/transport/unreachable-message.d.ts +1 -1
  113. package/dist/transport/unreachable-message.js +3 -5
  114. package/package.json +3 -2
  115. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  116. package/dist/presenter/carousel/offer-card.js +0 -55
@@ -0,0 +1,187 @@
1
+ import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT } from './presenter-account.js';
2
+ import { CHAT_ATTR, CHAT_PILL_ATTR, CHAT_PLACEMENT_ATTR, DOCK_ALIGN_ATTR, DOCK_ATTR, MIN_ATTR, REPORT_ATTR, REPORT_PANEL_ATTR, SETTINGS_ATTR, SETTINGS_PANEL_ATTR, SETTINGS_PLACEMENT_ATTR, CHAT_PANEL_ATTR, } from './presenter-config.js';
3
+ import { HUD_DROP_SHADOW, HUD_GLASS_PAINT } from './chrome/presenter-hud-chrome.js';
4
+ export const RAIL_ATTR = 'data-reticle-rail';
5
+ export const RAIL_SIGNIN_ATTR = 'data-reticle-rail-signin';
6
+ export const RAIL_PROMO_ATTR = 'data-reticle-rail-promo';
7
+ const PAGE_OPEN_ATTR = 'data-reticle-page-open';
8
+ const PAGE_PANEL_ATTR = 'data-reticle-page-panel';
9
+ const OVERLAY = 'data-reticle-overlay';
10
+ export const RAIL_TEXT = {
11
+ TITLE: 'Sign in to try Reticle Harness',
12
+ DETAIL: 'Free Harness credits to start.',
13
+ CTA: 'Sign in',
14
+ };
15
+ export const RAIL_HTML = `<div ${RAIL_ATTR} class="reticle-rail" role="complementary" aria-label="Reticle">
16
+ <div ${RAIL_SIGNIN_ATTR} class="reticle-rail-signin" hidden>
17
+ <span class="reticle-rail-copy"><strong>${RAIL_TEXT.TITLE}</strong><span>${RAIL_TEXT.DETAIL}</span></span>
18
+ <button type="button" ${ACCOUNT_SIGNIN_ATTR} class="reticle-rail-cta" title="${ACCOUNT_TEXT.SIGNIN_TITLE}">${RAIL_TEXT.CTA}</button>
19
+ </div>
20
+ <div ${RAIL_PROMO_ATTR} class="reticle-rail-promo"></div>
21
+ </div>`;
22
+ export function paintRail(root, account) {
23
+ const signin = root.querySelector(`[${RAIL_SIGNIN_ATTR}]`);
24
+ const promo = root.querySelector(`[${RAIL_PROMO_ATTR}]`);
25
+ const signedOut = account !== undefined && !account.signedIn;
26
+ if (signin !== null)
27
+ signin.hidden = !signedOut;
28
+ if (promo !== null)
29
+ promo.hidden = signedOut;
30
+ }
31
+ const PAGES = `:is([${CHAT_PANEL_ATTR}],[${PAGE_PANEL_ATTR}],[${REPORT_PANEL_ATTR}],[${SETTINGS_PANEL_ATTR}])`;
32
+ const ANY_OPEN = `[${OVERLAY}][${MIN_ATTR}="0"]:is([${CHAT_ATTR}="1"],[${PAGE_OPEN_ATTR}],[${REPORT_ATTR}="1"],[${SETTINGS_ATTR}="1"])`;
33
+ const BELOW = `[${DOCK_ATTR}]:is([${CHAT_PLACEMENT_ATTR}="below"],[${SETTINGS_PLACEMENT_ATTR}="below"])`;
34
+ export const FRAME_CSS = `
35
+ [${DOCK_ATTR}]{
36
+ /* Short enough to leave most of a laptop screen to the app; the log scrolls inside it. */
37
+ --reticle-frame-h:min(440px,60vh,calc(100vh - 190px));
38
+ --reticle-rail-h:52px;--reticle-frame-gap:6px;}
39
+ [${DOCK_ATTR}] ${PAGES}{
40
+ right:0;left:auto;top:auto;
41
+ bottom:calc(100% + 8px + var(--reticle-rail-h) + var(--reticle-frame-gap));
42
+ width:var(--reticle-dock-w);max-width:min(var(--reticle-dock-w),calc(100vw - 24px));
43
+ height:var(--reticle-frame-h);max-height:var(--reticle-frame-h);
44
+ border-radius:var(--reticle-hud-radius-lg);
45
+ ${HUD_GLASS_PAINT}}
46
+ [${DOCK_ATTR}] [${SETTINGS_PANEL_ATTR}]::after{display:none;}
47
+ [${DOCK_ATTR}] [${SETTINGS_PANEL_ATTR}] .reticle-settings-inner{height:100%;}
48
+ [${DOCK_ATTR}] [${SETTINGS_PANEL_ATTR}] :is(.reticle-settings-body,.reticle-settings-foot){background:transparent;}
49
+ [${DOCK_ATTR}] [${REPORT_PANEL_ATTR}] .reticle-report-inner{height:100%;}
50
+ /* The Agent Log section fills the frame, so the footer sits on the bottom edge, not mid-panel. */
51
+ [${CHAT_PANEL_ATTR}] .reticle-chat-view{flex:1;min-height:0;display:flex;flex-direction:column;}
52
+ [${CHAT_PANEL_ATTR}] .reticle-chat-view > .reticle-hud-log-well{flex:1;min-height:0;}
53
+ /*
54
+ * Two rows above the log in every state: the header and the status row. "Session ended" (or the
55
+ * handoff notice) takes the status text's place instead of arriving as a row of its own, and the
56
+ * Harness block sits in the footer above the project capsule.
57
+ */
58
+ [${OVERLAY}] [${CHAT_PANEL_ATTR}] .reticle-act-strip .reticle-banner{display:none;flex:1;min-width:0;min-height:0;padding:0;border:0;background:none;
59
+ font-size:11px;font-weight:600;line-height:1.3;color:var(--reticle-hud-text);
60
+ overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;}
61
+ [${OVERLAY}][data-reticle-state="ended"] [${CHAT_PANEL_ATTR}] .reticle-act-strip .reticle-banner{display:-webkit-box;}
62
+ [${OVERLAY}][data-reticle-state="ended"] [${CHAT_PANEL_ATTR}] .reticle-act-strip .reticle-act{display:none;}
63
+ [${CHAT_PANEL_ATTR}] .reticle-foot-workspace-row{gap:var(--reticle-hud-space-2);}
64
+ /* The Harness block sits above the project capsule, full width: one clear thing to press. */
65
+ [${CHAT_PANEL_ATTR}] [data-reticle-foot] .reticle-foot-stack{display:flex;flex-direction:column;gap:var(--reticle-hud-space-2);}
66
+ [${CHAT_PANEL_ATTR}] [data-reticle-foot] .reticle-foot-workspace-row:has(> .reticle-workspace-wrap[hidden]){display:none;}
67
+ ${BELOW} ${PAGES}{bottom:auto;top:calc(100% + 8px + var(--reticle-rail-h) + var(--reticle-frame-gap));}
68
+ [${DOCK_ATTR}][${DOCK_ALIGN_ATTR}="start"] :is(${PAGES},[${RAIL_ATTR}]){right:auto;left:0;}
69
+
70
+ /* ── Every page's header: title over a one-line subtitle, its actions, then close. ────────────── */
71
+ [${DOCK_ATTR}] :is(.reticle-page-heading,.reticle-report-head,.reticle-settings-head){
72
+ flex:none;display:flex;align-items:center;gap:var(--reticle-hud-space-2);min-height:52px;box-sizing:border-box;
73
+ padding:var(--reticle-hud-space-2) var(--reticle-hud-space-2) var(--reticle-hud-space-2) var(--reticle-hud-space-3);
74
+ border-bottom:1px solid var(--reticle-hud-border);background:none;}
75
+ [${DOCK_ATTR}] .reticle-page-titles{display:flex;flex:1;min-width:0;flex-direction:column;gap:1px;}
76
+ [${DOCK_ATTR}] :is(.reticle-page-heading strong,.reticle-report-title,.reticle-settings-title){
77
+ font-size:var(--reticle-hud-size-base);font-weight:600;letter-spacing:0;text-transform:none;color:var(--reticle-hud-text);}
78
+ [${DOCK_ATTR}] .reticle-settings-title{flex:1;}
79
+ [${DOCK_ATTR}] .reticle-page-heading .reticle-page-subtitle{padding:0;border:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
80
+ font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
81
+ [${DOCK_ATTR}] .reticle-primary-btn{flex:none;display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 var(--reticle-hud-space-3);
82
+ border:0;border-radius:var(--reticle-hud-radius-pill);background:var(--reticle-hud-brand);color:#160b02;
83
+ font:inherit;font-size:var(--reticle-hud-size-sm);font-weight:700;white-space:nowrap;cursor:pointer;}
84
+ [${DOCK_ATTR}] .reticle-primary-btn:hover{filter:brightness(1.08);}
85
+ [${DOCK_ATTR}] .reticle-page-heading .reticle-primary-btn{width:auto;height:28px;border-radius:var(--reticle-hud-radius-pill);background:var(--reticle-hud-brand);color:#160b02;font-size:var(--reticle-hud-size-sm);}
86
+ /* One close control everywhere: a quiet 28px square, the same glyph weight on every page. */
87
+ [${DOCK_ATTR}] :is([data-reticle-page-close],.reticle-report-close,.reticle-settings-close){flex:none;width:28px;height:28px;
88
+ display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:var(--reticle-hud-radius-sm);
89
+ background:transparent;color:var(--reticle-hud-text-muted);font-size:18px;line-height:1;cursor:pointer;}
90
+ [${DOCK_ATTR}] :is([data-reticle-page-close],.reticle-report-close,.reticle-settings-close):hover{background:var(--reticle-hud-hover);color:var(--reticle-hud-text);}
91
+ [${DOCK_ATTR}] .reticle-primary-btn[data-active="1"]{background:var(--reticle-hud-text);color:var(--reticle-hud-ground);}
92
+ [${DOCK_ATTR}] .reticle-primary-btn svg{display:block;fill:none;stroke:currentColor;stroke-width:1.8;}
93
+ /* One empty state for every page: what is missing, then how it gets filled. */
94
+ [${DOCK_ATTR}] :is(.reticle-flows-empty,.reticle-annotation-empty){display:flex;flex-direction:column;align-items:center;gap:6px;
95
+ margin:auto 0;padding:40px var(--reticle-hud-space-4);text-align:center;}
96
+ [${DOCK_ATTR}] :is(.reticle-flows-empty,.reticle-annotation-empty) strong{font-size:var(--reticle-hud-size-base);font-weight:600;color:var(--reticle-hud-text);}
97
+ [${DOCK_ATTR}] :is(.reticle-flows-empty,.reticle-annotation-empty) span{max-width:260px;font-size:var(--reticle-hud-size-sm);line-height:1.45;color:var(--reticle-hud-text-muted);}
98
+
99
+ /* ── Saved flows: one row per journey. ───────────────────────────────────────────────────────── */
100
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-all-flows{display:flex;flex-direction:column;gap:var(--reticle-hud-space-1);padding:var(--reticle-hud-space-2);}
101
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row{display:flex;align-items:center;gap:var(--reticle-hud-space-3);width:100%;min-height:48px;
102
+ padding:var(--reticle-hud-space-2) var(--reticle-hud-space-3);border:1px solid transparent;border-radius:var(--reticle-hud-radius-md);
103
+ background:transparent;color:var(--reticle-hud-text);font:inherit;text-align:left;cursor:pointer;}
104
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:hover:not(:disabled){background:var(--reticle-hud-hover);border-color:var(--reticle-hud-border);}
105
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:1px;}
106
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:disabled{cursor:not-allowed;opacity:.5;}
107
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-play{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;
108
+ border-radius:50%;background:var(--reticle-hud-brand-soft);color:var(--reticle-hud-brand);font-size:11px;}
109
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:disabled .reticle-flow-play{background:var(--reticle-hud-inset);color:var(--reticle-hud-text-faint);}
110
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-text{display:flex;flex:1;min-width:0;flex-direction:column;gap:1px;}
111
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-sm);font-weight:600;}
112
+ [${PAGE_PANEL_ATTR}="flows"] .reticle-flow-meta{font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
113
+
114
+ /* ── Notes: a segmented list switch, then the page's tools on the right. ─────────────────────── */
115
+ [${PAGE_PANEL_ATTR}="annotations"] .reticle-annotations-tabs{gap:var(--reticle-hud-space-1);padding:var(--reticle-hud-space-2) var(--reticle-hud-space-3);}
116
+ /* One segmented switch for every "show me this or that": Notes' list and Impact's scope. */
117
+ [${DOCK_ATTR}] .reticle-segmented{display:inline-flex;flex:none;padding:2px;border-radius:var(--reticle-hud-radius-sm);background:var(--reticle-hud-inset);}
118
+ [${PAGE_PANEL_ATTR}="annotations"] .reticle-segmented{margin-right:auto;}
119
+ [${DOCK_ATTR}] .reticle-segmented button{height:24px;padding:0 var(--reticle-hud-space-3);border:0;border-radius:4px;background:transparent;
120
+ color:var(--reticle-hud-text-muted);font:inherit;font-size:var(--reticle-hud-size-xs);white-space:nowrap;cursor:pointer;}
121
+ [${DOCK_ATTR}] .reticle-segmented button:hover{color:var(--reticle-hud-text);}
122
+ [${DOCK_ATTR}] .reticle-segmented button[aria-pressed="true"]{background:var(--reticle-hud-border-strong);color:var(--reticle-hud-text);}
123
+ [${DOCK_ATTR}] .reticle-report-head .reticle-segmented{margin-left:auto;}
124
+ [${PAGE_PANEL_ATTR}="annotations"] .reticle-annotations-tabs .reticle-copy-all{margin-left:0;color:var(--reticle-hud-text-muted);}
125
+ [${PAGE_PANEL_ATTR}="annotations"] .reticle-annotation-list{display:flex;flex-direction:column;}
126
+ [${PAGE_PANEL_ATTR}="annotations"] .reticle-annotation-item{padding:var(--reticle-hud-space-3) 2px;}
127
+
128
+ /* ── Impact: the streak and the account share the first line instead of a row each. ─────────── */
129
+ [${REPORT_PANEL_ATTR}] .reticle-report-top{display:flex;align-items:center;justify-content:space-between;gap:var(--reticle-hud-space-2);min-height:28px;margin-bottom:var(--reticle-hud-space-2);}
130
+ [${REPORT_PANEL_ATTR}] .reticle-report-top .reticle-report-streak{margin-bottom:0;}
131
+ [${REPORT_PANEL_ATTR}] .reticle-report-top .reticle-report-identity{margin:0 0 0 auto;padding:0;}
132
+ [${REPORT_PANEL_ATTR}] .reticle-report-hero-value{color:var(--reticle-hud-text);}
133
+ [${REPORT_PANEL_ATTR}] .reticle-report-card{background:var(--reticle-hud-inset);border-radius:var(--reticle-hud-radius-md);}
134
+ [${REPORT_PANEL_ATTR}] :is(.reticle-report-foot,.reticle-report-links){padding-left:var(--reticle-hud-space-3);padding-right:var(--reticle-hud-space-3);}
135
+
136
+ /* ── Settings: the help links scroll with the rest instead of pinning a footer over the options. */
137
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-body .reticle-settings-foot{margin-top:0;border-top:0;}
138
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-row{flex-wrap:wrap;}
139
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-helptext{flex-basis:100%;margin:6px 0 2px;font-size:var(--reticle-hud-size-xs);line-height:1.45;color:var(--reticle-hud-text-muted);}
140
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-help[aria-expanded="true"]{color:var(--reticle-hud-brand);}
141
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-reset{background:none;text-align:left;padding-left:14px;font-weight:500;}
142
+
143
+ /* ── Toolbar: Settings and Exit are tabs like the four beside them, so they light the same way. ── */
144
+ [data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn{width:100%;height:auto;flex-direction:column;gap:2px;min-height:42px;padding:4px 0 3px;
145
+ border-radius:var(--reticle-hud-radius-md);background:transparent;color:var(--reticle-hud-text-muted);}
146
+ [data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn:hover{background:var(--reticle-hud-hover);color:var(--reticle-hud-text);}
147
+ [data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn[data-active="1"],
148
+ [data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn[data-active="1"]:hover{background:transparent;color:var(--reticle-hud-text);}
149
+
150
+ [${RAIL_ATTR}]{
151
+ display:none;position:absolute;right:0;left:auto;bottom:calc(100% + 8px);z-index:7;
152
+ box-sizing:border-box;width:var(--reticle-dock-w);max-width:min(var(--reticle-dock-w),calc(100vw - 24px));
153
+ height:var(--reticle-rail-h);padding:0 var(--reticle-hud-space-3);align-items:center;
154
+ border:1px solid color-mix(in srgb,var(--reticle-hud-brand) 30%,var(--reticle-hud-border));
155
+ border-radius:var(--reticle-hud-radius-lg);
156
+ background:linear-gradient(135deg,var(--reticle-hud-brand-soft),var(--reticle-hud-surface) 65%);
157
+ box-shadow:${HUD_DROP_SHADOW};color:var(--reticle-hud-text);font-family:var(--reticle-font);
158
+ pointer-events:auto;}
159
+ ${BELOW} [${RAIL_ATTR}]{bottom:auto;top:calc(100% + 8px);}
160
+ ${ANY_OPEN} [${RAIL_ATTR}]{display:flex;}
161
+ /* The capsule and the rail share a slot: the capsule is for "nothing open", the rail for "a page is". */
162
+ ${ANY_OPEN} [${CHAT_PILL_ATTR}]{display:none !important;}
163
+ [${RAIL_ATTR}] > [hidden]{display:none !important;}
164
+ [${RAIL_ATTR}] .reticle-rail-signin,[${RAIL_ATTR}] .reticle-rail-promo{flex:1;min-width:0;}
165
+ [${RAIL_ATTR}] .reticle-rail-signin{display:flex;align-items:center;gap:var(--reticle-hud-space-3);}
166
+ [${RAIL_ATTR}] .reticle-rail-copy{display:flex;flex:1;min-width:0;flex-direction:column;line-height:1.3;}
167
+ [${RAIL_ATTR}] .reticle-rail-copy strong{font-size:var(--reticle-hud-size-sm);font-weight:600;}
168
+ [${RAIL_ATTR}] .reticle-rail-copy span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
169
+ [${RAIL_ATTR}] .reticle-rail-cta{flex:none;height:28px;padding:0 var(--reticle-hud-space-3);border:0;border-radius:var(--reticle-hud-radius-pill);
170
+ background:var(--reticle-hud-brand);color:#160b02;font:inherit;font-size:var(--reticle-hud-size-sm);font-weight:700;cursor:pointer;}
171
+ [${RAIL_ATTR}] .reticle-rail-cta:hover{filter:brightness(1.08);}
172
+ [${RAIL_ATTR}] .reticle-rail-cta:focus-visible{outline:2px solid var(--reticle-hud-text);outline-offset:2px;}
173
+ /* The promo: one line of copy, its link, and dots. The carousel draws no frame of its own here. */
174
+ [${RAIL_ATTR}] .reticle-carousel{margin:0;padding:0;border:0;background:none;}
175
+ [${RAIL_ATTR}] .reticle-carousel-track{height:32px;overflow:hidden;}
176
+ [${RAIL_ATTR}] :is(.reticle-carousel-close,.reticle-carousel-arrow,[data-reticle-carousel-position]){display:none;}
177
+ [${RAIL_ATTR}] .reticle-carousel-controls{position:absolute;right:var(--reticle-hud-space-3);top:6px;height:auto;margin:0;}
178
+ [${RAIL_ATTR}] .reticle-carousel-dots{position:static;display:flex;gap:4px;margin:0;}
179
+ [${RAIL_ATTR}] .reticle-carousel-dot{width:10px;height:3px;padding:0;border:0;border-radius:99px;background:var(--reticle-hud-text-muted);opacity:.35;cursor:pointer;}
180
+ [${RAIL_ATTR}] .reticle-carousel-dot[aria-current="true"]{background:var(--reticle-hud-brand);opacity:1;}
181
+ [${RAIL_ATTR}] .reticle-promo-copy{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:8px;padding-right:0;}
182
+ [${RAIL_ATTR}] .reticle-promo-kicker{display:none;}
183
+ [${RAIL_ATTR}] .reticle-promo-title{grid-column:1/-1;overflow:hidden;padding-right:40px;font-size:var(--reticle-hud-size-sm);font-weight:600;line-height:1.3;text-overflow:ellipsis;white-space:nowrap;}
184
+ [${RAIL_ATTR}] .reticle-promo-detail{grid-column:1;overflow:hidden;color:var(--reticle-hud-text-muted);font-size:var(--reticle-hud-size-xs);line-height:1.3;text-overflow:ellipsis;white-space:nowrap;}
185
+ [${RAIL_ATTR}] .reticle-promo-link{position:static;grid-column:2;color:var(--reticle-hud-brand);font-size:var(--reticle-hud-size-xs);font-weight:600;text-decoration:none;white-space:nowrap;}
186
+ [${RAIL_ATTR}] .reticle-promo-link:hover{text-decoration:underline;}
187
+ `;
@@ -0,0 +1,67 @@
1
+ import { type AccountState, type HarnessConfig, type HarnessDrive } from '@reticlehq/core';
2
+ import { type Credits } from '@reticlehq/core/hud';
3
+ export declare const HARNESS_ROW_TEXT: {
4
+ readonly RUN: "Run Harness";
5
+ readonly STARTING: "Starting…";
6
+ readonly STOP: "Stop";
7
+ readonly SIGNED_OUT: string;
8
+ readonly SIGN_IN: "Sign in";
9
+ readonly UNLINKED: "This project is not on your dashboard yet.";
10
+ readonly LINK: "Link this project";
11
+ readonly LINKING: "Linking…";
12
+ readonly NOT_ENTITLED: "Harness is not on your plan";
13
+ readonly NO_CREDITS: "No Harness credits left";
14
+ readonly NO_PROVIDER: "No model provider set";
15
+ readonly OFF: "Harness is off";
16
+ readonly canSwitchOn: (percent: number) => string;
17
+ readonly TURN_ON: "Switch on";
18
+ readonly COPY_PROMPT: "Copy setup prompt";
19
+ readonly COPIED: "Copied";
20
+ readonly SET_UP: "Set up ↗";
21
+ readonly PLANS: "See plans ↗";
22
+ readonly PLAN: "Plan ↗";
23
+ readonly MORE: "Get more ↗";
24
+ readonly driving: (as: string | undefined, steps: number) => string;
25
+ readonly NAME: "Harness";
26
+ readonly TOGGLE: "Reticle Harness on this project";
27
+ readonly creditsLeft: (left: number, days: number | undefined) => string;
28
+ readonly FOOT_LINK: "Link project";
29
+ readonly FOOT_NO_CREDITS: "No credits";
30
+ readonly FOOT_NOT_ENTITLED: "Not on your plan";
31
+ readonly FOOT_NO_PROVIDER: "No model provider";
32
+ readonly footSetup: (percent: number) => string;
33
+ readonly PAGE_TITLE: "Reticle Harness";
34
+ readonly LOG_LABEL: "What the Harness did";
35
+ readonly LOG_EMPTY: "Runs show here, step by step.";
36
+ readonly PAGE_SUBTITLE: "Tests your app by itself";
37
+ };
38
+ export declare const HARNESS_SWITCH_ON_ATTR = "data-reticle-harness-switch";
39
+ export declare const HARNESS_LINK_ATTR = "data-reticle-harness-link";
40
+ export declare const HARNESS_OPEN_ATTR = "data-reticle-harness-open";
41
+ export declare const HARNESS_LOG_ATTR = "data-reticle-harness-log";
42
+ export declare const HARNESS_TOGGLE_ATTR = "data-reticle-harness-toggle";
43
+ export declare const HARNESS_COPY_PROMPT_ATTR = "data-reticle-harness-copy-prompt";
44
+ export interface HarnessGateView {
45
+ percent: number;
46
+ unlocked: boolean;
47
+ reason?: string;
48
+ prompt?: string;
49
+ }
50
+ export declare function harnessGateIn(instrumentation: Readonly<Record<string, unknown>> | undefined, config?: HarnessConfig): HarnessGateView | undefined;
51
+ export interface HarnessRowState {
52
+ account: AccountState | undefined;
53
+ config: HarnessConfig | undefined;
54
+ gate?: HarnessGateView | undefined;
55
+ drive: HarnessDrive | undefined;
56
+ canDrive: boolean;
57
+ pick: string;
58
+ startedAs: string | undefined;
59
+ setupUrl: string;
60
+ planUrl: string;
61
+ linking?: boolean;
62
+ starting?: boolean;
63
+ }
64
+ export declare function creditsShort(credits: Credits | undefined): string;
65
+ export declare function harnessFootHtml(s: HarnessRowState): string;
66
+ export declare function harnessRowHtml(s: HarnessRowState): string;
67
+ export declare const HARNESS_ROW_CSS = "\n[data-reticle-chat-panel] .reticle-harness-spot{flex:none;display:flex;flex-direction:column;gap:4px;}\n[data-reticle-chat-panel] .reticle-harness-spot[hidden],[data-reticle-chat-panel] .reticle-harness-spot:empty{display:none;}\n[data-reticle-chat-panel] .reticle-harness-row{display:flex;align-items:center;gap:6px;min-height:30px;min-width:0;}\n[data-reticle-chat-panel] .reticle-harness-lead{flex:1;min-width:0;display:flex;align-items:center;gap:6px;}\n[data-reticle-chat-panel] .reticle-harness-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);}\n[data-reticle-chat-panel] .reticle-harness-row-wrap{flex-wrap:wrap;row-gap:2px;padding:4px 0;}\n[data-reticle-chat-panel] .reticle-harness-row-wrap .reticle-harness-said{flex:1 1 100%;white-space:normal;}\n[data-reticle-chat-panel] .reticle-harness-said code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;}\n[data-reticle-chat-panel] .reticle-harness-as{flex:none;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}\n[data-reticle-chat-panel] .reticle-harness-persona{box-sizing:border-box;flex:1;min-width:0;width:100%;height:30px;font:inherit;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text);\n background:var(--reticle-hud-ground);border:1px solid var(--reticle-hud-border-strong);border-radius:var(--reticle-hud-radius-sm);padding:0 var(--reticle-hud-space-2);}\n[data-reticle-chat-panel] .reticle-harness-spot > .reticle-harness-persona{flex:none;}\n[data-reticle-chat-panel] select.reticle-harness-persona{padding-right:2px;text-overflow:ellipsis;}\n[data-reticle-chat-panel] .reticle-harness-persona::placeholder{color:var(--reticle-hud-text-faint);}\n[data-reticle-chat-panel] .reticle-harness-persona:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}\n[data-reticle-chat-panel] .reticle-harness-run{flex:none;box-sizing:border-box;height:30px;padding:0 12px;border:0;border-radius:var(--reticle-hud-radius-sm);cursor:pointer;white-space:nowrap;\n background:var(--reticle-hud-brand);color:#160b02;font:inherit;font-size:var(--reticle-hud-size-sm);font-weight:700;}\n[data-reticle-chat-panel] .reticle-harness-run:hover{filter:brightness(1.08);}\n[data-reticle-chat-panel] .reticle-harness-run:focus-visible{outline:2px solid var(--reticle-hud-text);outline-offset:2px;}\n[data-reticle-chat-panel] .reticle-harness-stop{background:transparent;color:var(--reticle-hud-text);border:1px solid var(--reticle-hud-border-strong);}\n[data-reticle-chat-panel] .reticle-harness-link{flex:none;border:0;padding:0;background:none;cursor:pointer;font:inherit;color:var(--reticle-hud-brand);font-size:var(--reticle-hud-size-sm);font-weight:600;text-decoration:none;white-space:nowrap;}\n[data-reticle-chat-panel] .reticle-harness-link:hover{text-decoration:underline;}\n[data-reticle-chat-panel] .reticle-harness-open{flex:1;min-width:0;height:30px;display:flex;align-items:center;gap:8px;padding:0 var(--reticle-hud-space-2);border:0;border-radius:var(--reticle-hud-radius-sm);background:transparent;cursor:pointer;\n text-align:left;white-space:nowrap;font:inherit;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text);}\n[data-reticle-chat-panel] .reticle-harness-name{flex:none;font-weight:600;}\n[data-reticle-chat-panel] .reticle-harness-state{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;color:var(--reticle-hud-text-muted);font-size:11px;font-variant-numeric:tabular-nums;}\n[data-reticle-chat-panel] .reticle-harness-chev{flex:none;color:var(--reticle-hud-text-faint);}\n[data-reticle-chat-panel] .reticle-harness-toggle{flex:none;position:relative;width:30px;height:18px;padding:0;border:0;border-radius:9px;cursor:pointer;background:var(--reticle-hud-border-strong);transition:background .15s;}\n[data-reticle-chat-panel] .reticle-harness-toggle[aria-checked=\"true\"]{background:var(--reticle-hud-brand);}\n[data-reticle-chat-panel] .reticle-harness-knob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:transform .15s;}\n[data-reticle-chat-panel] .reticle-harness-toggle[aria-checked=\"true\"] .reticle-harness-knob{transform:translateX(12px);}\n[data-reticle-chat-panel] .reticle-harness-toggle:focus-visible{outline:2px solid var(--reticle-hud-text);outline-offset:2px;}\n[data-reticle-chat-panel] .reticle-harness-open:hover{background:var(--reticle-hud-hover);}\n[data-reticle-chat-panel] .reticle-harness-open:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}\n[data-reticle-chat-panel] .reticle-harness-run:disabled,[data-reticle-chat-panel] .reticle-harness-persona:disabled{opacity:.6;cursor:default;}\n[data-reticle-chat-panel] .reticle-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}\n[data-reticle-chat-panel] .reticle-foot-meta{margin:0;font-size:11px;line-height:1.3;color:var(--reticle-hud-text-muted);overflow-wrap:anywhere;}\n[data-reticle-chat-panel] .reticle-foot-meta:empty{display:none;}\n";
@@ -0,0 +1,209 @@
1
+ import { CreditKind, } from '@reticlehq/core';
2
+ import { SIGN_UP_PITCH, creditsSpent, creditsUsageText, creditsUsedText, } from '@reticlehq/core/hud';
3
+ import { esc } from './chrome/presenter-safe-html.js';
4
+ import { ACCOUNT_SIGNIN_ATTR } from './presenter-account.js';
5
+ import { personaPickHtml, personaCustomHtml, personaLabel } from './presenter-personas.js';
6
+ export const HARNESS_ROW_TEXT = {
7
+ RUN: 'Run Harness',
8
+ STARTING: 'Starting…',
9
+ STOP: 'Stop',
10
+ SIGNED_OUT: SIGN_UP_PITCH,
11
+ SIGN_IN: 'Sign in',
12
+ UNLINKED: 'This project is not on your dashboard yet.',
13
+ LINK: 'Link this project',
14
+ LINKING: 'Linking…',
15
+ NOT_ENTITLED: 'Harness is not on your plan',
16
+ NO_CREDITS: 'No Harness credits left',
17
+ NO_PROVIDER: 'No model provider set',
18
+ OFF: 'Harness is off',
19
+ canSwitchOn: (percent) => `Setup score ${String(percent)}%. Harness is off.`,
20
+ TURN_ON: 'Switch on',
21
+ COPY_PROMPT: 'Copy setup prompt',
22
+ COPIED: 'Copied',
23
+ SET_UP: 'Set up ↗',
24
+ PLANS: 'See plans ↗',
25
+ PLAN: 'Plan ↗',
26
+ MORE: 'Get more ↗',
27
+ driving: (as, steps) => `Driving${as === undefined ? '' : ` as ${as}`} · ${String(steps)} step${1 === steps ? '' : 's'}`,
28
+ NAME: 'Harness',
29
+ TOGGLE: 'Reticle Harness on this project',
30
+ creditsLeft: (left, days) => `${left.toLocaleString('en-US')} credit${1 === left ? '' : 's'}${days === undefined ? '' : ` · ${String(days)} day${1 === days ? '' : 's'}`}`,
31
+ FOOT_LINK: 'Link project',
32
+ FOOT_NO_CREDITS: 'No credits',
33
+ FOOT_NOT_ENTITLED: 'Not on your plan',
34
+ FOOT_NO_PROVIDER: 'No model provider',
35
+ footSetup: (percent) => `Setup ${String(percent)}% · needs 80%`,
36
+ PAGE_TITLE: 'Reticle Harness',
37
+ LOG_LABEL: 'What the Harness did',
38
+ LOG_EMPTY: 'Runs show here, step by step.',
39
+ PAGE_SUBTITLE: 'Tests your app by itself',
40
+ };
41
+ export const HARNESS_SWITCH_ON_ATTR = 'data-reticle-harness-switch';
42
+ export const HARNESS_LINK_ATTR = 'data-reticle-harness-link';
43
+ export const HARNESS_OPEN_ATTR = 'data-reticle-harness-open';
44
+ export const HARNESS_LOG_ATTR = 'data-reticle-harness-log';
45
+ export const HARNESS_TOGGLE_ATTR = 'data-reticle-harness-toggle';
46
+ export const HARNESS_COPY_PROMPT_ATTR = 'data-reticle-harness-copy-prompt';
47
+ export function harnessGateIn(instrumentation, config) {
48
+ const local = localGateIn(instrumentation);
49
+ const platform = config?.gate;
50
+ if (platform === undefined)
51
+ return local;
52
+ const prompt = platform.prompt ?? local?.prompt;
53
+ const reason = platform.unlocked ? undefined : (platform.reason ?? local?.reason);
54
+ return {
55
+ percent: platform.percent ?? local?.percent ?? 0,
56
+ unlocked: platform.unlocked,
57
+ ...(reason === undefined ? {} : { reason }),
58
+ ...(prompt === undefined ? {} : { prompt }),
59
+ };
60
+ }
61
+ function localGateIn(instrumentation) {
62
+ const raw = instrumentation?.['harnessGate'];
63
+ if ('object' !== typeof raw || null === raw)
64
+ return undefined;
65
+ const gate = raw;
66
+ const percent = gate['percent'];
67
+ const unlocked = gate['unlocked'];
68
+ if ('number' !== typeof percent || 'boolean' !== typeof unlocked)
69
+ return undefined;
70
+ const reason = gate['reason'];
71
+ const prompt = instrumentation?.['prompt'];
72
+ return {
73
+ percent: Math.round(percent),
74
+ unlocked,
75
+ ...('string' === typeof reason ? { reason } : {}),
76
+ ...('string' === typeof prompt ? { prompt } : {}),
77
+ };
78
+ }
79
+ const row = (inner) => `<div class="reticle-harness-row">${inner}</div>`;
80
+ const wrapRow = (inner) => `<div class="reticle-harness-row reticle-harness-row-wrap">${inner}</div>`;
81
+ const said = (text) => `<span class="reticle-harness-said">${text}</span>`;
82
+ const runRow = (lead, button) => row(`<span class="reticle-harness-lead">${lead}</span>${button}`);
83
+ const STOP_BUTTON = `<button type="button" data-reticle-harness-stop class="reticle-harness-run reticle-harness-stop">${HARNESS_ROW_TEXT.STOP}</button>`;
84
+ const link = (href, label) => `<a class="reticle-harness-link" href="${href}" target="_blank" rel="noopener noreferrer">${label}</a>`;
85
+ export function creditsShort(credits) {
86
+ return credits === undefined || creditsSpent(credits) ? '' : creditsUsageText(credits);
87
+ }
88
+ function creditsUsedRow(credits, planUrl) {
89
+ if (credits.kind === undefined)
90
+ return row(`${said(HARNESS_ROW_TEXT.NO_CREDITS)}${link(planUrl, HARNESS_ROW_TEXT.MORE)}`);
91
+ const text = creditsUsedText(credits);
92
+ const action = text.action !== undefined
93
+ ? link(planUrl, `${text.action} ↗`)
94
+ : CreditKind.TRIAL === credits.kind
95
+ ? link(planUrl, HARNESS_ROW_TEXT.PLAN)
96
+ : '';
97
+ return wrapRow(`${said(text.said)}${action}`);
98
+ }
99
+ function footState(s) {
100
+ if (true === s.starting)
101
+ return HARNESS_ROW_TEXT.STARTING;
102
+ const gate = s.gate;
103
+ if (gate !== undefined && !gate.unlocked && gate.reason !== undefined)
104
+ return HARNESS_ROW_TEXT.footSetup(gate.percent);
105
+ const config = s.config;
106
+ if (config === undefined)
107
+ return HARNESS_ROW_TEXT.FOOT_LINK;
108
+ if (config.credits !== undefined && creditsSpent(config.credits))
109
+ return HARNESS_ROW_TEXT.FOOT_NO_CREDITS;
110
+ if (!config.harnessEntitled)
111
+ return HARNESS_ROW_TEXT.FOOT_NOT_ENTITLED;
112
+ if (false === config.providerReady)
113
+ return HARNESS_ROW_TEXT.FOOT_NO_PROVIDER;
114
+ const credits = config.credits;
115
+ return credits === undefined
116
+ ? ''
117
+ : HARNESS_ROW_TEXT.creditsLeft(Math.max(0, credits.limit - credits.used), credits.daysLeft);
118
+ }
119
+ function switchable(s) {
120
+ const config = s.config;
121
+ if (config === undefined)
122
+ return false;
123
+ if (s.gate !== undefined && !s.gate.unlocked && s.gate.reason !== undefined)
124
+ return false;
125
+ if (config.credits !== undefined && creditsSpent(config.credits))
126
+ return false;
127
+ return config.harnessEntitled && false !== config.providerReady;
128
+ }
129
+ export function harnessFootHtml(s) {
130
+ if (s.account === undefined || !s.account.signedIn)
131
+ return '';
132
+ if (s.drive !== undefined && s.canDrive)
133
+ return row(`<button type="button" ${HARNESS_OPEN_ATTR} class="reticle-harness-open"><span class="reticle-harness-name">${HARNESS_ROW_TEXT.NAME}</span><span class="reticle-harness-state">${esc(HARNESS_ROW_TEXT.driving(s.startedAs === undefined ? undefined : personaLabel(s.startedAs), s.drive.steps))}</span><span class="reticle-harness-chev" aria-hidden="true">›</span></button>${STOP_BUTTON}`);
134
+ const on = true === s.config?.harnessEnabled;
135
+ const credits = s.config?.credits;
136
+ const title = credits === undefined ? '' : ` title="${esc(creditsUsageText(credits))}"`;
137
+ const toggle = switchable(s)
138
+ ? `<button type="button" role="switch" ${HARNESS_TOGGLE_ATTR} class="reticle-harness-toggle" aria-checked="${String(on)}" aria-label="${HARNESS_ROW_TEXT.TOGGLE}"><span class="reticle-harness-knob"></span></button>`
139
+ : '';
140
+ return row(`<button type="button" ${HARNESS_OPEN_ATTR} class="reticle-harness-open"${title}><span class="reticle-harness-name">${HARNESS_ROW_TEXT.NAME}</span><span class="reticle-harness-state">${esc(footState(s))}</span><span class="reticle-harness-chev" aria-hidden="true">›</span></button>${toggle}`);
141
+ }
142
+ export function harnessRowHtml(s) {
143
+ if (s.account === undefined)
144
+ return '';
145
+ if (!s.account.signedIn)
146
+ return wrapRow(`${said(HARNESS_ROW_TEXT.SIGNED_OUT)}<button type="button" ${ACCOUNT_SIGNIN_ATTR} class="reticle-harness-link">${HARNESS_ROW_TEXT.SIGN_IN}</button>`);
147
+ if (s.drive !== undefined && s.canDrive)
148
+ return (runRow(said(HARNESS_ROW_TEXT.driving(s.startedAs === undefined ? undefined : personaLabel(s.startedAs), s.drive.steps)), STOP_BUTTON) + personaCustomHtml(s.pick, true));
149
+ const gate = s.gate;
150
+ if (gate !== undefined && !gate.unlocked && gate.reason !== undefined)
151
+ return wrapRow(`${said(esc(gate.reason))}${gate.prompt === undefined
152
+ ? ''
153
+ : `<button type="button" ${HARNESS_COPY_PROMPT_ATTR} class="reticle-harness-link">${HARNESS_ROW_TEXT.COPY_PROMPT}</button>`}`);
154
+ const config = s.config;
155
+ if (config === undefined)
156
+ return wrapRow(`${said(HARNESS_ROW_TEXT.UNLINKED)}<button type="button" ${HARNESS_LINK_ATTR} class="reticle-harness-run"${true === s.linking ? ' disabled' : ''}>${true === s.linking ? HARNESS_ROW_TEXT.LINKING : HARNESS_ROW_TEXT.LINK}</button>`);
157
+ if (config.credits !== undefined && creditsSpent(config.credits))
158
+ return creditsUsedRow(config.credits, s.planUrl);
159
+ if (!config.harnessEntitled)
160
+ return row(`${said(HARNESS_ROW_TEXT.NOT_ENTITLED)}${link(s.planUrl, HARNESS_ROW_TEXT.PLANS)}`);
161
+ if (false === config.providerReady)
162
+ return row(`${said(HARNESS_ROW_TEXT.NO_PROVIDER)}${link(s.setupUrl, HARNESS_ROW_TEXT.SET_UP)}`);
163
+ if (!config.harnessEnabled)
164
+ return row(`${said(gate === undefined ? HARNESS_ROW_TEXT.OFF : HARNESS_ROW_TEXT.canSwitchOn(gate.percent))}<button type="button" ${HARNESS_SWITCH_ON_ATTR} class="reticle-harness-run">${HARNESS_ROW_TEXT.TURN_ON}</button>`);
165
+ if (!s.canDrive)
166
+ return '';
167
+ const starting = true === s.starting;
168
+ return (runRow(personaPickHtml(s.pick, starting), `<button type="button" data-reticle-harness-run class="reticle-harness-run"${starting ? ' disabled' : ''}>${starting ? HARNESS_ROW_TEXT.STARTING : HARNESS_ROW_TEXT.RUN}</button>`) + personaCustomHtml(s.pick, starting));
169
+ }
170
+ export const HARNESS_ROW_CSS = `
171
+ [data-reticle-chat-panel] .reticle-harness-spot{flex:none;display:flex;flex-direction:column;gap:4px;}
172
+ [data-reticle-chat-panel] .reticle-harness-spot[hidden],[data-reticle-chat-panel] .reticle-harness-spot:empty{display:none;}
173
+ [data-reticle-chat-panel] .reticle-harness-row{display:flex;align-items:center;gap:6px;min-height:30px;min-width:0;}
174
+ [data-reticle-chat-panel] .reticle-harness-lead{flex:1;min-width:0;display:flex;align-items:center;gap:6px;}
175
+ [data-reticle-chat-panel] .reticle-harness-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);}
176
+ [data-reticle-chat-panel] .reticle-harness-row-wrap{flex-wrap:wrap;row-gap:2px;padding:4px 0;}
177
+ [data-reticle-chat-panel] .reticle-harness-row-wrap .reticle-harness-said{flex:1 1 100%;white-space:normal;}
178
+ [data-reticle-chat-panel] .reticle-harness-said code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;}
179
+ [data-reticle-chat-panel] .reticle-harness-as{flex:none;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
180
+ [data-reticle-chat-panel] .reticle-harness-persona{box-sizing:border-box;flex:1;min-width:0;width:100%;height:30px;font:inherit;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text);
181
+ background:var(--reticle-hud-ground);border:1px solid var(--reticle-hud-border-strong);border-radius:var(--reticle-hud-radius-sm);padding:0 var(--reticle-hud-space-2);}
182
+ [data-reticle-chat-panel] .reticle-harness-spot > .reticle-harness-persona{flex:none;}
183
+ [data-reticle-chat-panel] select.reticle-harness-persona{padding-right:2px;text-overflow:ellipsis;}
184
+ [data-reticle-chat-panel] .reticle-harness-persona::placeholder{color:var(--reticle-hud-text-faint);}
185
+ [data-reticle-chat-panel] .reticle-harness-persona:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}
186
+ [data-reticle-chat-panel] .reticle-harness-run{flex:none;box-sizing:border-box;height:30px;padding:0 12px;border:0;border-radius:var(--reticle-hud-radius-sm);cursor:pointer;white-space:nowrap;
187
+ background:var(--reticle-hud-brand);color:#160b02;font:inherit;font-size:var(--reticle-hud-size-sm);font-weight:700;}
188
+ [data-reticle-chat-panel] .reticle-harness-run:hover{filter:brightness(1.08);}
189
+ [data-reticle-chat-panel] .reticle-harness-run:focus-visible{outline:2px solid var(--reticle-hud-text);outline-offset:2px;}
190
+ [data-reticle-chat-panel] .reticle-harness-stop{background:transparent;color:var(--reticle-hud-text);border:1px solid var(--reticle-hud-border-strong);}
191
+ [data-reticle-chat-panel] .reticle-harness-link{flex:none;border:0;padding:0;background:none;cursor:pointer;font:inherit;color:var(--reticle-hud-brand);font-size:var(--reticle-hud-size-sm);font-weight:600;text-decoration:none;white-space:nowrap;}
192
+ [data-reticle-chat-panel] .reticle-harness-link:hover{text-decoration:underline;}
193
+ [data-reticle-chat-panel] .reticle-harness-open{flex:1;min-width:0;height:30px;display:flex;align-items:center;gap:8px;padding:0 var(--reticle-hud-space-2);border:0;border-radius:var(--reticle-hud-radius-sm);background:transparent;cursor:pointer;
194
+ text-align:left;white-space:nowrap;font:inherit;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text);}
195
+ [data-reticle-chat-panel] .reticle-harness-name{flex:none;font-weight:600;}
196
+ [data-reticle-chat-panel] .reticle-harness-state{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;color:var(--reticle-hud-text-muted);font-size:11px;font-variant-numeric:tabular-nums;}
197
+ [data-reticle-chat-panel] .reticle-harness-chev{flex:none;color:var(--reticle-hud-text-faint);}
198
+ [data-reticle-chat-panel] .reticle-harness-toggle{flex:none;position:relative;width:30px;height:18px;padding:0;border:0;border-radius:9px;cursor:pointer;background:var(--reticle-hud-border-strong);transition:background .15s;}
199
+ [data-reticle-chat-panel] .reticle-harness-toggle[aria-checked="true"]{background:var(--reticle-hud-brand);}
200
+ [data-reticle-chat-panel] .reticle-harness-knob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:transform .15s;}
201
+ [data-reticle-chat-panel] .reticle-harness-toggle[aria-checked="true"] .reticle-harness-knob{transform:translateX(12px);}
202
+ [data-reticle-chat-panel] .reticle-harness-toggle:focus-visible{outline:2px solid var(--reticle-hud-text);outline-offset:2px;}
203
+ [data-reticle-chat-panel] .reticle-harness-open:hover{background:var(--reticle-hud-hover);}
204
+ [data-reticle-chat-panel] .reticle-harness-open:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}
205
+ [data-reticle-chat-panel] .reticle-harness-run:disabled,[data-reticle-chat-panel] .reticle-harness-persona:disabled{opacity:.6;cursor:default;}
206
+ [data-reticle-chat-panel] .reticle-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}
207
+ [data-reticle-chat-panel] .reticle-foot-meta{margin:0;font-size:11px;line-height:1.3;color:var(--reticle-hud-text-muted);overflow-wrap:anywhere;}
208
+ [data-reticle-chat-panel] .reticle-foot-meta:empty{display:none;}
209
+ `;
@@ -0,0 +1,35 @@
1
+ export declare const CUSTOM_PERSONA = "custom";
2
+ export declare const PERSONAS: readonly [{
3
+ readonly id: "first-time";
4
+ readonly label: "First-time visitor";
5
+ readonly hint: "starts from the first page, reads the labels and follows the obvious path";
6
+ }, {
7
+ readonly id: "power-user";
8
+ readonly label: "Returning power user";
9
+ readonly hint: "goes straight to the main task, takes shortcuts and repeats it quickly";
10
+ }, {
11
+ readonly id: "phone";
12
+ readonly label: "On a phone, slow connection";
13
+ readonly hint: "small-screen habits: taps, waits on loading, retries when nothing seems to happen";
14
+ }, {
15
+ readonly id: "keyboard";
16
+ readonly label: "Keyboard only";
17
+ readonly hint: "Tab, Enter and Escape only: focus order, traps and controls it cannot reach";
18
+ }, {
19
+ readonly id: "careless";
20
+ readonly label: "Careless user (bad and unexpected input)";
21
+ readonly hint: "empty fields, wrong formats, very long text, double submits, going back mid-flow";
22
+ }, {
23
+ readonly id: "admin";
24
+ readonly label: "Admin / settings";
25
+ readonly hint: "changes settings and permissions, then checks they stick after a reload";
26
+ }];
27
+ export declare const DEFAULT_PERSONA: "first-time";
28
+ export declare const PERSONA_PICK_ID = "reticle-harness-persona-pick";
29
+ export declare function personaText(pick: string, custom: string): string | undefined;
30
+ export declare function personaLabel(pick: string): string;
31
+ export declare function personaPickHtml(pick: string, disabled?: boolean): string;
32
+ export declare function personaCustomHtml(pick: string, disabled?: boolean): string;
33
+ export declare function knownPick(value: string | null | undefined): string | undefined;
34
+ export declare function readPick(projectId: string | undefined): string;
35
+ export declare function savePick(projectId: string | undefined, pick: string): void;
@@ -0,0 +1,95 @@
1
+ import { PERSONA_KEY_PREFIX } from '../storage-keys.js';
2
+ import { esc } from './chrome/presenter-safe-html.js';
3
+ export const CUSTOM_PERSONA = 'custom';
4
+ export const PERSONAS = [
5
+ {
6
+ id: 'first-time',
7
+ label: 'First-time visitor',
8
+ hint: 'starts from the first page, reads the labels and follows the obvious path',
9
+ },
10
+ {
11
+ id: 'power-user',
12
+ label: 'Returning power user',
13
+ hint: 'goes straight to the main task, takes shortcuts and repeats it quickly',
14
+ },
15
+ {
16
+ id: 'phone',
17
+ label: 'On a phone, slow connection',
18
+ hint: 'small-screen habits: taps, waits on loading, retries when nothing seems to happen',
19
+ },
20
+ {
21
+ id: 'keyboard',
22
+ label: 'Keyboard only',
23
+ hint: 'Tab, Enter and Escape only: focus order, traps and controls it cannot reach',
24
+ },
25
+ {
26
+ id: 'careless',
27
+ label: 'Careless user (bad and unexpected input)',
28
+ hint: 'empty fields, wrong formats, very long text, double submits, going back mid-flow',
29
+ },
30
+ {
31
+ id: 'admin',
32
+ label: 'Admin / settings',
33
+ hint: 'changes settings and permissions, then checks they stick after a reload',
34
+ },
35
+ ];
36
+ export const DEFAULT_PERSONA = PERSONAS[0].id;
37
+ const TEXT = {
38
+ LABEL: 'As',
39
+ CUSTOM: 'Custom…',
40
+ CUSTOM_SHORT: 'Custom',
41
+ CUSTOM_HINT: 'Describe who to be or what to try, in your own words.',
42
+ CUSTOM_PLACEHOLDER: 'e.g. a first-time shopper paying by card',
43
+ };
44
+ export const PERSONA_PICK_ID = 'reticle-harness-persona-pick';
45
+ const PERSONA_TEXT_ID = 'reticle-harness-persona';
46
+ function presetOf(pick) {
47
+ return PERSONAS.find((persona) => persona.id === pick);
48
+ }
49
+ export function personaText(pick, custom) {
50
+ const preset = presetOf(pick);
51
+ if (preset !== undefined)
52
+ return `${preset.label}: ${preset.hint}`;
53
+ const own = custom.trim();
54
+ return 0 === own.length ? undefined : own;
55
+ }
56
+ export function personaLabel(pick) {
57
+ return presetOf(pick)?.label ?? TEXT.CUSTOM_SHORT;
58
+ }
59
+ export function personaPickHtml(pick, disabled = false) {
60
+ const options = [
61
+ ...PERSONAS.map((p) => ({ id: p.id, label: p.label, hint: p.hint })),
62
+ { id: CUSTOM_PERSONA, label: TEXT.CUSTOM, hint: TEXT.CUSTOM_HINT },
63
+ ]
64
+ .map((o) => `<option value="${o.id}" title="${esc(o.hint)}"${o.id === pick ? ' selected' : ''}>${esc(o.label)}</option>`)
65
+ .join('');
66
+ const hint = esc(presetOf(pick)?.hint ?? TEXT.CUSTOM_HINT);
67
+ return (`<label class="reticle-harness-as" for="${PERSONA_PICK_ID}">${TEXT.LABEL}</label>` +
68
+ `<select id="${PERSONA_PICK_ID}" data-reticle-harness-persona-pick class="reticle-harness-persona" title="${hint}" aria-describedby="${PERSONA_PICK_ID}-hint"${disabled ? ' disabled' : ''}>${options}</select>` +
69
+ `<span id="${PERSONA_PICK_ID}-hint" class="reticle-sr">${hint}</span>`);
70
+ }
71
+ export function personaCustomHtml(pick, disabled = false) {
72
+ return CUSTOM_PERSONA === pick
73
+ ? `<input type="text" id="${PERSONA_TEXT_ID}" data-reticle-harness-persona class="reticle-harness-persona" aria-label="${TEXT.CUSTOM_HINT}" placeholder="${TEXT.CUSTOM_PLACEHOLDER}"${disabled ? ' disabled' : ''}>`
74
+ : '';
75
+ }
76
+ export function knownPick(value) {
77
+ return value !== null && value !== undefined && (CUSTOM_PERSONA === value || presetOf(value))
78
+ ? value
79
+ : undefined;
80
+ }
81
+ export function readPick(projectId) {
82
+ try {
83
+ return (knownPick(localStorage.getItem(`${PERSONA_KEY_PREFIX}${projectId ?? ''}`)) ?? DEFAULT_PERSONA);
84
+ }
85
+ catch {
86
+ return DEFAULT_PERSONA;
87
+ }
88
+ }
89
+ export function savePick(projectId, pick) {
90
+ try {
91
+ localStorage.setItem(`${PERSONA_KEY_PREFIX}${projectId ?? ''}`, pick);
92
+ }
93
+ catch {
94
+ }
95
+ }
@@ -0,0 +1,10 @@
1
+ import { type PlanView } from '@reticlehq/core';
2
+ export declare const PLAN_ATTR = "data-reticle-plan";
3
+ export declare const PLAN_HTML = "<div data-reticle-plan class=\"reticle-plan\" hidden></div>";
4
+ export declare const PLAN_CSS: string;
5
+ export declare function parsePlanView(args: Record<string, unknown>): PlanView | undefined;
6
+ export declare class PlanBoard {
7
+ #private;
8
+ mount(root: HTMLElement): void;
9
+ paint(view: PlanView): void;
10
+ }