@reticlehq/browser 2.3.0 → 2.5.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.
- package/dist/actions/actions-dom.js +10 -10
- package/dist/actions/actions.d.ts +13 -0
- package/dist/actions/actions.js +45 -15
- package/dist/actions/scroll.js +1 -1
- package/dist/commands/commands.js +39 -22
- package/dist/connect-options.d.ts +103 -0
- package/dist/connect-options.js +1 -0
- package/dist/dom/a11y.js +20 -20
- package/dist/dom/auto-anchor.js +1 -1
- package/dist/dom/blind-spots.js +1 -1
- package/dist/dom/chart.js +5 -5
- package/dist/dom/desktop-capture.d.ts +2 -1
- package/dist/dom/desktop-capture.js +4 -4
- package/dist/dom/occlusion.js +2 -2
- package/dist/dom/query.js +28 -18
- package/dist/dom/realm.js +13 -13
- package/dist/dom/refs.d.ts +14 -0
- package/dist/dom/refs.js +20 -1
- package/dist/dom/shadow-registry.js +2 -2
- package/dist/dom/snapshot.js +7 -7
- package/dist/dom/source.js +3 -3
- package/dist/dom/theme.js +6 -6
- package/dist/dom/virtualized.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/observers/blind-spots.js +2 -2
- package/dist/observers/console.js +3 -3
- package/dist/observers/dom.js +5 -5
- package/dist/observers/download.d.ts +2 -1
- package/dist/observers/download.js +2 -2
- package/dist/observers/focus.js +1 -1
- package/dist/observers/frames.js +1 -1
- package/dist/observers/health.js +70 -2
- package/dist/observers/install-all.d.ts +2 -1
- package/dist/observers/ipc.d.ts +2 -7
- package/dist/observers/ipc.js +7 -7
- package/dist/observers/network-body.js +2 -2
- package/dist/observers/network-redact.js +5 -5
- package/dist/observers/network-stream.js +2 -2
- package/dist/observers/network.d.ts +3 -3
- package/dist/observers/network.js +9 -9
- package/dist/observers/perf.js +1 -1
- package/dist/observers/scroll.js +1 -1
- package/dist/observers/state.js +1 -1
- package/dist/observers/storage.js +7 -7
- package/dist/presenter/presenter-brand.d.ts +19 -0
- package/dist/presenter/presenter-brand.js +21 -0
- package/dist/presenter/presenter-config.d.ts +17 -0
- package/dist/presenter/presenter-config.js +21 -0
- package/dist/presenter/presenter-controls.d.ts +1 -11
- package/dist/presenter/presenter-controls.js +6 -7
- package/dist/presenter/presenter-log.js +2 -2
- package/dist/presenter/presenter-run-state.js +1 -1
- package/dist/presenter/presenter-styles.d.ts +1 -1
- package/dist/presenter/presenter-styles.js +20 -9
- package/dist/presenter/presenter-tally.js +3 -3
- package/dist/presenter/presenter-test-helpers.d.ts +2 -1
- package/dist/presenter/presenter-test-helpers.js +2 -2
- package/dist/presenter/presenter-verbs.js +16 -11
- package/dist/presenter/presenter.js +13 -10
- package/dist/recorder/recorder.js +10 -9
- package/dist/registry/capabilities.d.ts +2 -0
- package/dist/registry/capabilities.js +16 -0
- package/dist/registry/store-adapters.d.ts +127 -1
- package/dist/registry/store-adapters.js +178 -0
- package/dist/registry/stores.js +3 -3
- package/dist/reticle-presenter-helpers.js +3 -3
- package/dist/reticle.d.ts +2 -57
- package/dist/reticle.js +57 -14
- package/dist/review/annotator.js +7 -7
- package/dist/security/serialization.js +9 -9
- package/dist/session-continuity.d.ts +17 -0
- package/dist/session-continuity.js +42 -0
- package/dist/timers/clock.js +4 -4
- package/dist/timers/native-console.js +1 -1
- package/dist/timers/native-timers.js +4 -4
- package/dist/transport/transport.d.ts +10 -0
- package/dist/transport/transport.js +19 -5
- package/package.json +11 -4
|
@@ -25,7 +25,7 @@ export function buildRunState(input) {
|
|
|
25
25
|
}
|
|
26
26
|
return {
|
|
27
27
|
session: input.sessionId,
|
|
28
|
-
url: typeof location
|
|
28
|
+
url: 'undefined' === typeof location ? '' : location.href,
|
|
29
29
|
state: input.state,
|
|
30
30
|
startedMs: start,
|
|
31
31
|
durationMs: Math.max(0, (input.endMs ?? input.now) - start),
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Split out of presenter.ts so that file stays a cohesive controller under the size cap; this is
|
|
4
4
|
* pure style data (no behavior). LOG_CSS + CONTROLS_CSS are composed in at the end as before.
|
|
5
5
|
*/
|
|
6
|
-
export declare const PRESENTER_CSS = "\n[data-reticle-glow]{position:fixed;inset:0;pointer-events:none;z-index:2147483600;opacity:0;\n transition:opacity .25s ease;box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 28px 6px rgba(99,102,241,.45);}\n[data-reticle-glow][data-on=\"1\"]{opacity:1;animation:reticle-pulse 1.6s ease-in-out infinite;}\n[data-reticle-glow][data-on=\"1\"][data-busy=\"1\"]{animation:reticle-shimmer 1.1s ease-in-out infinite;}\n@keyframes reticle-pulse{0%,100%{box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 22px 4px rgba(99,102,241,.35)}\n 50%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 40px 10px rgba(99,102,241,.6)}}\n@keyframes reticle-shimmer{0%,100%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 34px 8px rgba(99,102,241,.55)}\n 50%{box-shadow:inset 0 0 0 3px rgba(140,142,255,1),inset 0 0 48px 12px rgba(99,102,241,.7)}}\n[data-reticle-cursor]{position:fixed;top:0;left:0;width:22px;height:22px;margin:-11px 0 0 -11px;\n border:2px solid #6366f1;border-radius:50%;background:rgba(99,102,241,.25);pointer-events:none;\n z-index:2147483646;opacity:0;transition:transform .32s cubic-bezier(.22,1,.36,1),opacity .2s ease;}\n[data-reticle-cursor][data-on=\"1\"]{opacity:1;}\n[data-reticle-cursor]::after{content:\"\";position:absolute;inset:7px;border-radius:50%;background:#6366f1;}\n[data-reticle-ripple]{position:fixed;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;\n background:rgba(99,102,241,.5);pointer-events:none;z-index:2147483645;animation:reticle-ripple .5s ease-out forwards;}\n@keyframes reticle-ripple{from{transform:scale(.4);opacity:.8}to{transform:scale(5);opacity:0}}\n[data-reticle-ring]{position:fixed;pointer-events:none;z-index:2147483644;border:2px solid #22c55e;border-radius:8px;\n box-shadow:0 0 0 3px rgba(34,197,94,.25);opacity:0;transition:opacity .15s ease;}\n[data-reticle-ring][data-on=\"1\"]{opacity:1;}\n[data-reticle-hud]{\n --reticle-accent:#7c83ff;--reticle-accent-soft:rgba(124,131,255,.16);\n --reticle-bg:rgba(13,15,22,.92);--reticle-bg2:rgba(19,22,32,.88);\n --reticle-fg:#e9ebf2;--reticle-muted:#9aa0b2;--reticle-faint:#6a7186;\n --reticle-line:rgba(255,255,255,.09);--reticle-line2:rgba(255,255,255,.05);\n --reticle-read:#54d2e6;--reticle-ok:#3dd7a6;--reticle-bad:#ff7a7a;\n --reticle-font:\"Inter\",system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;\n --reticle-serif:\"IBM Plex Serif\",Georgia,\"Times New Roman\",serif;\n position:fixed;left:50%;right:auto;bottom:20px;box-sizing:border-box;\n width:384px;height:468px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);\n display:flex;flex-direction:column;overflow:hidden;text-align:left;z-index:2147483647;pointer-events:none;\n font-family:var(--reticle-font);font-size:13px;line-height:1.5;color:var(--reticle-fg);-webkit-font-smoothing:antialiased;\n background:linear-gradient(180deg,var(--reticle-bg),var(--reticle-bg2));\n /* No backdrop-filter here, deliberately. blur(24px) on this panel was measured as the single most\n expensive thing the whole SDK does: a backdrop blur must re-sample everything behind it whenever\n that content changes, and on a page repainting at 60fps that is continuous full-panel work. On\n the hostile fixture it cost +4.0pp of main thread \u2014 five times the rest of the presenter, and\n more than every observer combined. Removing it took total main-thread time from 2.15s to 1.67s\n over an 8s window. The panel background is ~85% opaque, so the glass effect it bought was\n marginal. If you want it back, add it here and expect to pay that. */\n border:1px solid var(--reticle-line);border-radius:20px;\n box-shadow:0 28px 70px -18px rgba(0,0,0,.66),0 0 0 1px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.07),0 0 54px -22px var(--reticle-accent);\n opacity:0;transform:translateX(-50%) translateY(14px) scale(.985);\n transition:opacity .3s ease,transform .42s cubic-bezier(.16,1,.3,1),height .42s cubic-bezier(.16,1,.3,1),border-radius .42s ease,box-shadow .35s ease;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-hud]{--reticle-accent:#f6b44c;--reticle-accent-soft:rgba(246,180,76,.16);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud]{--reticle-accent:#3dd7a6;--reticle-accent-soft:rgba(61,215,166,.14);}\n[data-reticle-hud]::before{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;\n background:radial-gradient(130% 90% at 50% 0%,var(--reticle-accent-soft),transparent 60%);}\n[data-reticle-hud]>*{position:relative;}\n[data-reticle-hud][data-on=\"1\"]{opacity:1;transform:translateX(-50%) translateY(0) scale(1);}\n/* Click-through: the glassy panel itself never blocks the app \u2014 only its interactive controls\n capture clicks (buttons / inputs), so a human can click straight through the HUD to the page.\n The log auto-scrolls, so it stays click-through too (drag-scroll is traded for click-through). */\n[data-reticle-hud] button,[data-reticle-hud] input,[data-reticle-hud] textarea,\n[data-reticle-hud] select,[data-reticle-hud] [contenteditable]{pointer-events:auto;}\n/* When minimised to a pill, the whole bar is the (single) click target to restore. */\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud][data-on=\"1\"]{pointer-events:auto;}\n[data-reticle-hud] .reticle-hud-head{display:flex;align-items:center;gap:8px;flex:none;\n padding:12px 12px 12px 15px;border-bottom:1px solid var(--reticle-line2);}\n[data-reticle-hud] .reticle-dot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--reticle-accent);\n animation:reticle-breathe 2.6s ease-in-out infinite;}\n@keyframes reticle-breathe{0%,100%{box-shadow:0 0 0 0 var(--reticle-accent),0 0 7px 1px var(--reticle-accent);opacity:.85}\n 50%{box-shadow:0 0 0 4px var(--reticle-accent-soft),0 0 15px 3px var(--reticle-accent);opacity:1}}\n[data-reticle-hud] .reticle-brand{font-family:var(--reticle-serif);font-weight:500;font-size:15px;letter-spacing:.01em;color:var(--reticle-fg);}\n[data-reticle-hud] .reticle-head-sp{flex:1;}\n[data-reticle-hud] .reticle-live{display:none;flex:1;min-width:0;color:var(--reticle-muted);font-size:12.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] .reticle-maxhint{display:none;flex:none;color:var(--reticle-faint);font-size:13px;line-height:1;}\n[data-reticle-hud] .reticle-act-strip{flex:none;padding:7px 15px;border-bottom:1px solid var(--reticle-line2);background:rgba(0,0,0,.14);}\n[data-reticle-hud] .reticle-act{display:block;color:var(--reticle-muted);font-size:11.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] [data-reticle-min-btn]{flex:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;\n width:26px;height:26px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-size:15px;line-height:1;transition:background .15s,color .15s,transform .1s;}\n[data-reticle-hud] [data-reticle-min-btn]:hover{color:var(--reticle-fg);background:rgba(255,255,255,.08);}\n[data-reticle-hud] [data-reticle-min-btn]:active{transform:scale(.94);}\n[data-reticle-hud] .reticle-pass{color:var(--reticle-ok);}[data-reticle-hud] .reticle-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally{flex:none;display:inline-flex;align-items:center;gap:8px;font-size:11.5px;\n font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:.01em;}\n[data-reticle-hud] .reticle-tally[hidden]{display:none;}\n[data-reticle-hud] .reticle-tally .reticle-t-pass{color:var(--reticle-ok);}\n[data-reticle-hud] .reticle-tally .reticle-t-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally [data-z=\"1\"]{opacity:.4;}\n@keyframes reticle-tally-pop{0%{transform:scale(1)}38%{transform:scale(1.3)}100%{transform:scale(1)}}\n[data-reticle-hud] .reticle-tally [data-bump=\"1\"]{display:inline-block;animation:reticle-tally-pop .36s cubic-bezier(.16,1,.3,1);}\n[data-reticle-hud] .reticle-chip{display:none;flex:none;font-size:9px;font-weight:600;letter-spacing:.08em;\n padding:2px 7px;border-radius:6px;vertical-align:middle;}\n[data-reticle-hud] .reticle-chip[data-mode=\"reading\"]{display:inline-block;color:var(--reticle-read);\n background:rgba(84,210,230,.12);border:1px solid rgba(84,210,230,.32);}\n[data-reticle-hud] .reticle-chip[data-mode=\"acting\"]{display:inline-block;color:var(--reticle-accent);\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);}\n[data-reticle-hud] .reticle-chip[data-mode=\"idle\"]{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud]{height:50px;border-radius:25px;cursor:pointer;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-hud-head{border-bottom:none;height:50px;padding:0 12px 0 16px;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-brand,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-chip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-head-sp,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-min-btn],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-ctl,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-badge,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-act-strip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-log],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-foot],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-banner{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-live{display:block;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-maxhint{display:inline-flex;}\n[data-reticle-mode=\"reading\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(34,211,238,.9),inset 0 0 28px 6px rgba(34,211,238,.4);}\n[data-reticle-mode=\"reading\"] [data-reticle-ring]{border-color:#22d3ee;\n box-shadow:0 0 0 3px rgba(34,211,238,.25);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(251,191,36,.9),inset 0 0 28px 6px rgba(251,191,36,.45);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-hud]{--reticle-accent:#fbbf24;--reticle-accent-soft:rgba(251,191,36,.16);}\n\n[data-reticle-log]{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;\n gap:7px;padding:12px 14px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;}\n[data-reticle-log]::-webkit-scrollbar{width:9px;}\n[data-reticle-log]::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-log]::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.26);background-clip:content-box;}\n[data-reticle-log-row]{display:flex;align-items:baseline;gap:8px;font-size:12px;line-height:1.45;\n animation:reticle-row-in .26s cubic-bezier(.16,1,.3,1);}\n@keyframes reticle-row-in{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}\n[data-reticle-log-ts]{flex:none;color:var(--reticle-faint);font-size:10px;font-variant-numeric:tabular-nums;padding-top:1px;}\n[data-reticle-log] .reticle-log-text{flex:1;min-width:0;color:#d6dae4;overflow-wrap:anywhere;word-break:break-word;}\n[data-reticle-log] .reticle-res{flex:none;font-weight:700;}\n[data-reticle-log-row][data-kind=\"human\"]{align-self:flex-end;max-width:88%;\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);border-radius:13px 13px 4px 13px;padding:6px 11px;}\n[data-reticle-log-row][data-kind=\"human\"] [data-reticle-log-ts]{display:none;}\n[data-reticle-log-row][data-kind=\"human\"] .reticle-log-text{color:var(--reticle-fg);}\n\n\n[data-reticle-hud] .reticle-ctl{pointer-events:auto;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;\n height:26px;padding:0 11px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-family:var(--reticle-font);font-size:11px;font-weight:500;letter-spacing:.01em;line-height:1;\n transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-ctl:hover{color:var(--reticle-fg);background:rgba(255,255,255,.09);}\n[data-reticle-hud] .reticle-ctl:active{transform:scale(.95);}\n[data-reticle-hud] .reticle-ctl:disabled{opacity:.35;cursor:default;}\n[data-reticle-hud] [data-reticle-end]{color:#ff9aa2;border-color:rgba(255,107,107,.22);}\n[data-reticle-hud] [data-reticle-end]:hover{color:#ff7a7a;border-color:rgba(255,107,107,.5);background:rgba(255,107,107,.1);}\n[data-reticle-hud] .reticle-badge{display:none;align-items:center;flex:none;font-weight:600;letter-spacing:.1em;font-size:9px;\n color:var(--reticle-accent);border:1px solid var(--reticle-accent);background:var(--reticle-accent-soft);padding:2px 8px;border-radius:999px;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-badge]{display:inline-flex;}\n[data-reticle-hud] [data-reticle-foot]{flex:none;padding:10px 12px 12px;border-top:1px solid var(--reticle-line2);background:rgba(0,0,0,.16);}\n[data-reticle-hud] .reticle-composer{display:flex;align-items:flex-end;gap:6px;background:rgba(255,255,255,.05);\n border:1px solid var(--reticle-line);border-radius:14px;padding:5px 6px 5px 14px;transition:border-color .15s,box-shadow .15s;}\n[data-reticle-hud] .reticle-composer:focus-within{border-color:var(--reticle-accent);box-shadow:0 0 0 3px var(--reticle-accent-soft);}\n[data-reticle-hud] .reticle-msg{flex:1;min-width:0;pointer-events:auto;background:transparent;border:none;outline:none;resize:none;\n box-sizing:border-box;color:var(--reticle-fg);font-family:var(--reticle-font);font-size:13px;line-height:18px;\n height:28px;min-height:28px;max-height:96px;padding:5px 0;overflow-y:auto;\n scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-msg::placeholder{color:var(--reticle-faint);}\n[data-reticle-hud] .reticle-msg:disabled{opacity:.5;}\n[data-reticle-hud] .reticle-send{flex:none;width:30px;height:30px;padding:0;border-radius:10px;border:none;cursor:pointer;pointer-events:auto;\n background:var(--reticle-accent);color:#0b0d14;display:inline-flex;align-items:center;justify-content:center;transition:filter .15s,transform .1s;}\n[data-reticle-hud] .reticle-send svg{display:block;}\n[data-reticle-hud] .reticle-send:hover{filter:brightness(1.12);}\n[data-reticle-hud] .reticle-send:active{transform:scale(.9);}\n[data-reticle-hud] .reticle-send:disabled{opacity:.4;cursor:default;}\n[data-reticle-hud] .reticle-banner{display:none;flex:none;padding:8px 15px;color:var(--reticle-accent);\n font-size:11.5px;font-weight:500;border-bottom:1px solid var(--reticle-line2);background:var(--reticle-accent-soft);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-banner]{display:block;}\n/* Export row: hidden during a live session; revealed when ended so the run can be copied/saved. */\n[data-reticle-hud] .reticle-export{display:none;align-items:center;gap:8px;margin-top:9px;}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud] .reticle-export{display:flex;}\n[data-reticle-hud] .reticle-export-msg{color:var(--reticle-ok);font-size:11px;opacity:0;transition:opacity .15s;}\n[data-reticle-hud] .reticle-export-msg[data-show=\"1\"]{opacity:1;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 3px rgba(246,180,76,.9),inset 0 0 30px 6px rgba(246,180,76,.4);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(61,215,166,.55);}\n/* Handoff tones tell the human the agent's mode at a glance. waiting = calm teal \"your turn\" (no\n alarm); ask = amber \"answer me\" with a pulse; warn = amber \"agent crashed\" with a pulse. Each leads\n the banner with an icon and overrides the calm ended-green accent. */\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-hud]{--reticle-accent:#38bdf8;--reticle-accent-soft:rgba(56,189,248,.16);}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]{font-weight:600;color:#7dd3fc;}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]::before{content:\"\\270B \";}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(56,189,248,.5);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-hud],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-hud]{--reticle-accent:#fb923c;--reticle-accent-soft:rgba(251,146,60,.18);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]{font-weight:600;color:#fdba74;}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner]::before{content:\"\\2753 \";}\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]::before{content:\"\\26A0\\FE0F \";}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-glow][data-on=\"1\"],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-glow][data-on=\"1\"]{animation:reticle-warn-pulse 1.5s ease-in-out infinite;\n box-shadow:inset 0 0 0 2px rgba(251,146,60,.7);}\n@keyframes reticle-warn-pulse{0%,100%{box-shadow:inset 0 0 0 2px rgba(251,146,60,.32);}\n 50%{box-shadow:inset 0 0 0 3px rgba(251,146,60,.85),inset 0 0 26px 5px rgba(251,146,60,.34);}}\n/* Replay-a-flow row: the human re-runs a saved flow with no agent. Hidden until flows are pushed.\n Bounded + self-scrolling: it sits between the flex:1 log and the flex:none composer, so without a\n height cap a long flow list would squeeze the log to nothing and push the message input past the\n panel's overflow:hidden clip. flex:none + max-height + overflow-y keep the log and input always\n visible; extra flow chips scroll inside this section instead of growing the panel. */\n[data-reticle-hud] .reticle-flows{display:none;flex:none;flex-wrap:wrap;align-content:flex-start;gap:6px;\n padding:9px 12px;border-top:1px solid var(--reticle-line2);max-height:88px;overflow-y:auto;overscroll-behavior:contain;}\n[data-reticle-hud] .reticle-flows[data-has=\"1\"]{display:flex;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-flows-cap{flex:0 0 100%;margin-bottom:1px;color:var(--reticle-faint);font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;}\n[data-reticle-hud] .reticle-flow{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;\n border-radius:7px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);color:var(--reticle-muted);\n font-family:var(--reticle-font);font-size:11px;font-weight:500;transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-flow:hover{color:var(--reticle-fg);background:var(--reticle-accent-soft);border-color:var(--reticle-accent);}\n[data-reticle-hud] .reticle-flow:active{transform:scale(.95);}\n";
|
|
6
|
+
export declare const PRESENTER_CSS = "\n[data-reticle-glow]{position:fixed;inset:0;pointer-events:none;z-index:2147483600;opacity:0;\n transition:opacity .25s ease;box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 28px 6px rgba(99,102,241,.45);}\n[data-reticle-glow][data-on=\"1\"]{opacity:1;animation:reticle-pulse 1.6s ease-in-out infinite;}\n[data-reticle-glow][data-on=\"1\"][data-busy=\"1\"]{animation:reticle-shimmer 1.1s ease-in-out infinite;}\n@keyframes reticle-pulse{0%,100%{box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 22px 4px rgba(99,102,241,.35)}\n 50%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 40px 10px rgba(99,102,241,.6)}}\n@keyframes reticle-shimmer{0%,100%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 34px 8px rgba(99,102,241,.55)}\n 50%{box-shadow:inset 0 0 0 3px rgba(140,142,255,1),inset 0 0 48px 12px rgba(99,102,241,.7)}}\n[data-reticle-cursor]{position:fixed;top:0;left:0;width:22px;height:22px;margin:-11px 0 0 -11px;\n border:2px solid #6366f1;border-radius:50%;background:rgba(99,102,241,.25);pointer-events:none;\n z-index:2147483646;opacity:0;transition:transform .32s cubic-bezier(.22,1,.36,1),opacity .2s ease;}\n[data-reticle-cursor][data-on=\"1\"]{opacity:1;}\n[data-reticle-cursor]::after{content:\"\";position:absolute;inset:7px;border-radius:50%;background:#6366f1;}\n[data-reticle-ripple]{position:fixed;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;\n background:rgba(99,102,241,.5);pointer-events:none;z-index:2147483645;animation:reticle-ripple .5s ease-out forwards;}\n@keyframes reticle-ripple{from{transform:scale(.4);opacity:.8}to{transform:scale(5);opacity:0}}\n[data-reticle-ring]{position:fixed;pointer-events:none;z-index:2147483644;border:2px solid #22c55e;border-radius:8px;\n box-shadow:0 0 0 3px rgba(34,197,94,.25);opacity:0;transition:opacity .15s ease;}\n[data-reticle-ring][data-on=\"1\"]{opacity:1;}\n[data-reticle-hud]{\n --reticle-accent:#7c83ff;--reticle-accent-soft:rgba(124,131,255,.16);\n --reticle-bg:rgba(13,15,22,.92);--reticle-bg2:rgba(19,22,32,.88);\n --reticle-fg:#e9ebf2;--reticle-muted:#9aa0b2;--reticle-faint:#6a7186;\n --reticle-line:rgba(255,255,255,.09);--reticle-line2:rgba(255,255,255,.05);\n --reticle-read:#54d2e6;--reticle-ok:#3dd7a6;--reticle-bad:#ff7a7a;\n --reticle-font:\"Inter\",system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;\n position:fixed;left:50%;right:auto;bottom:20px;box-sizing:border-box;\n width:384px;height:468px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);\n display:flex;flex-direction:column;overflow:hidden;text-align:left;z-index:2147483647;pointer-events:none;\n font-family:var(--reticle-font);font-size:13px;line-height:1.5;color:var(--reticle-fg);-webkit-font-smoothing:antialiased;\n background:linear-gradient(180deg,var(--reticle-bg),var(--reticle-bg2));\n /* No backdrop-filter here, deliberately. blur(24px) on this panel was measured as the single most\n expensive thing the whole SDK does: a backdrop blur must re-sample everything behind it whenever\n that content changes, and on a page repainting at 60fps that is continuous full-panel work. On\n the hostile fixture it cost +4.0pp of main thread \u2014 five times the rest of the presenter, and\n more than every observer combined. Removing it took total main-thread time from 2.15s to 1.67s\n over an 8s window. The panel background is ~85% opaque, so the glass effect it bought was\n marginal. If you want it back, add it here and expect to pay that. */\n border:1px solid var(--reticle-line);border-radius:20px;\n box-shadow:0 28px 70px -18px rgba(0,0,0,.66),0 0 0 1px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.07),0 0 54px -22px var(--reticle-accent);\n opacity:0;transform:translateX(-50%) translateY(14px) scale(.985);\n transition:opacity .3s ease,transform .42s cubic-bezier(.16,1,.3,1),height .42s cubic-bezier(.16,1,.3,1),border-radius .42s ease,box-shadow .35s ease;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-hud]{--reticle-accent:#f6b44c;--reticle-accent-soft:rgba(246,180,76,.16);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud]{--reticle-accent:#3dd7a6;--reticle-accent-soft:rgba(61,215,166,.14);}\n[data-reticle-hud]::before{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;\n background:radial-gradient(130% 90% at 50% 0%,var(--reticle-accent-soft),transparent 60%);}\n[data-reticle-hud]>*{position:relative;}\n[data-reticle-hud][data-on=\"1\"]{opacity:1;transform:translateX(-50%) translateY(0) scale(1);}\n/* Click-through: the glassy panel itself never blocks the app \u2014 only its interactive controls\n capture clicks (buttons / inputs), so a human can click straight through the HUD to the page.\n The log auto-scrolls, so it stays click-through too (drag-scroll is traded for click-through). */\n[data-reticle-hud] button,[data-reticle-hud] input,[data-reticle-hud] textarea,\n[data-reticle-hud] select,[data-reticle-hud] [contenteditable]{pointer-events:auto;}\n/* When minimised to a pill, the whole bar is the (single) click target to restore. */\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud][data-on=\"1\"]{pointer-events:auto;}\n[data-reticle-hud] .reticle-hud-head{display:flex;align-items:center;gap:8px;flex:none;\n padding:12px 12px 12px 15px;border-bottom:1px solid var(--reticle-line2);}\n/* Brand block: both marks always in the DOM, CSS picks one off data-reticle-min (see below) so the\n collapsed/expanded swap needs no JS state. Artwork paints with currentColor, so it inherits\n --reticle-fg and reads correctly on any HUD surface instead of baking in the export's #FAFAFA. */\n[data-reticle-hud] .reticle-brand{display:inline-flex;align-items:center;flex:none;color:var(--reticle-fg);}\n[data-reticle-hud] .reticle-mark{display:none;height:18px;width:auto;flex:none;}\n[data-reticle-hud] .reticle-wordmark{display:block;height:16px;width:auto;flex:none;}\n/* Visually hidden, still announced \u2014 the marks are aria-hidden artwork, this names the HUD. */\n[data-reticle-hud] .reticle-brand-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;\n overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;}\n[data-reticle-hud] .reticle-head-sp{flex:1;}\n[data-reticle-hud] .reticle-live{display:none;flex:1;min-width:0;color:var(--reticle-muted);font-size:12.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] .reticle-maxhint{display:none;flex:none;color:var(--reticle-faint);font-size:13px;line-height:1;}\n[data-reticle-hud] .reticle-act-strip{flex:none;padding:7px 15px;border-bottom:1px solid var(--reticle-line2);background:rgba(0,0,0,.14);}\n[data-reticle-hud] .reticle-act{display:block;color:var(--reticle-muted);font-size:11.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] [data-reticle-min-btn]{flex:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;\n width:26px;height:26px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-size:15px;line-height:1;transition:background .15s,color .15s,transform .1s;}\n[data-reticle-hud] [data-reticle-min-btn]:hover{color:var(--reticle-fg);background:rgba(255,255,255,.08);}\n[data-reticle-hud] [data-reticle-min-btn]:active{transform:scale(.94);}\n[data-reticle-hud] .reticle-pass{color:var(--reticle-ok);}[data-reticle-hud] .reticle-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally{flex:none;display:inline-flex;align-items:center;gap:8px;font-size:11.5px;\n font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:.01em;}\n[data-reticle-hud] .reticle-tally[hidden]{display:none;}\n[data-reticle-hud] .reticle-tally .reticle-t-pass{color:var(--reticle-ok);}\n[data-reticle-hud] .reticle-tally .reticle-t-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally [data-z=\"1\"]{opacity:.4;}\n@keyframes reticle-tally-pop{0%{transform:scale(1)}38%{transform:scale(1.3)}100%{transform:scale(1)}}\n[data-reticle-hud] .reticle-tally [data-bump=\"1\"]{display:inline-block;animation:reticle-tally-pop .36s cubic-bezier(.16,1,.3,1);}\n[data-reticle-hud] .reticle-chip{display:none;flex:none;font-size:9px;font-weight:600;letter-spacing:.08em;\n padding:2px 7px;border-radius:6px;vertical-align:middle;}\n[data-reticle-hud] .reticle-chip[data-mode=\"reading\"]{display:inline-block;color:var(--reticle-read);\n background:rgba(84,210,230,.12);border:1px solid rgba(84,210,230,.32);}\n[data-reticle-hud] .reticle-chip[data-mode=\"acting\"]{display:inline-block;color:var(--reticle-accent);\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);}\n[data-reticle-hud] .reticle-chip[data-mode=\"idle\"]{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud]{height:50px;border-radius:25px;cursor:pointer;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-hud-head{border-bottom:none;height:50px;padding:0 12px 0 16px;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-chip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-head-sp,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-min-btn],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-ctl,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-badge,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-act-strip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-log],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-foot],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-banner{display:none;}\n/* Collapsed to a pill: mark only, beside the dot. Expanded: the full wordmark. */\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-wordmark{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-mark{display:block;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-live{display:block;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-maxhint{display:inline-flex;}\n[data-reticle-mode=\"reading\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(34,211,238,.9),inset 0 0 28px 6px rgba(34,211,238,.4);}\n[data-reticle-mode=\"reading\"] [data-reticle-ring]{border-color:#22d3ee;\n box-shadow:0 0 0 3px rgba(34,211,238,.25);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(251,191,36,.9),inset 0 0 28px 6px rgba(251,191,36,.45);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-hud]{--reticle-accent:#fbbf24;--reticle-accent-soft:rgba(251,191,36,.16);}\n/* The two INFINITE animations \u2014 the header dot and the border glow \u2014 are pure decoration and were\n blinking regardless of the user's motion preference. Everything else the presenter animates is a\n one-shot (ripple, tally pop) or a transition, which reduce-motion tolerates. */\n@media (prefers-reduced-motion:reduce){\n [data-reticle-glow][data-on=\"1\"],\n [data-reticle-glow][data-on=\"1\"][data-busy=\"1\"]{animation:none;}}\n\n[data-reticle-log]{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;\n gap:7px;padding:12px 14px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;}\n[data-reticle-log]::-webkit-scrollbar{width:9px;}\n[data-reticle-log]::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-log]::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.26);background-clip:content-box;}\n[data-reticle-log-row]{display:flex;align-items:baseline;gap:8px;font-size:12px;line-height:1.45;\n animation:reticle-row-in .26s cubic-bezier(.16,1,.3,1);}\n@keyframes reticle-row-in{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}\n[data-reticle-log-ts]{flex:none;color:var(--reticle-faint);font-size:10px;font-variant-numeric:tabular-nums;padding-top:1px;}\n[data-reticle-log] .reticle-log-text{flex:1;min-width:0;color:#d6dae4;overflow-wrap:anywhere;word-break:break-word;}\n[data-reticle-log] .reticle-res{flex:none;font-weight:700;}\n[data-reticle-log-row][data-kind=\"human\"]{align-self:flex-end;max-width:88%;\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);border-radius:13px 13px 4px 13px;padding:6px 11px;}\n[data-reticle-log-row][data-kind=\"human\"] [data-reticle-log-ts]{display:none;}\n[data-reticle-log-row][data-kind=\"human\"] .reticle-log-text{color:var(--reticle-fg);}\n\n\n[data-reticle-hud] .reticle-ctl{pointer-events:auto;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;\n height:26px;padding:0 11px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-family:var(--reticle-font);font-size:11px;font-weight:500;letter-spacing:.01em;line-height:1;\n transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-ctl:hover{color:var(--reticle-fg);background:rgba(255,255,255,.09);}\n[data-reticle-hud] .reticle-ctl:active{transform:scale(.95);}\n[data-reticle-hud] .reticle-ctl:disabled{opacity:.35;cursor:default;}\n[data-reticle-hud] [data-reticle-end]{color:#ff9aa2;border-color:rgba(255,107,107,.22);}\n[data-reticle-hud] [data-reticle-end]:hover{color:#ff7a7a;border-color:rgba(255,107,107,.5);background:rgba(255,107,107,.1);}\n[data-reticle-hud] .reticle-badge{display:none;align-items:center;flex:none;font-weight:600;letter-spacing:.1em;font-size:9px;\n color:var(--reticle-accent);border:1px solid var(--reticle-accent);background:var(--reticle-accent-soft);padding:2px 8px;border-radius:999px;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-badge]{display:inline-flex;}\n[data-reticle-hud] [data-reticle-foot]{flex:none;padding:10px 12px 12px;border-top:1px solid var(--reticle-line2);background:rgba(0,0,0,.16);}\n[data-reticle-hud] .reticle-composer{display:flex;align-items:flex-end;gap:6px;background:rgba(255,255,255,.05);\n border:1px solid var(--reticle-line);border-radius:14px;padding:5px 6px 5px 14px;transition:border-color .15s,box-shadow .15s;}\n[data-reticle-hud] .reticle-composer:focus-within{border-color:var(--reticle-accent);box-shadow:0 0 0 3px var(--reticle-accent-soft);}\n[data-reticle-hud] .reticle-msg{flex:1;min-width:0;pointer-events:auto;background:transparent;border:none;outline:none;resize:none;\n box-sizing:border-box;color:var(--reticle-fg);font-family:var(--reticle-font);font-size:13px;line-height:18px;\n height:28px;min-height:28px;max-height:96px;padding:5px 0;overflow-y:auto;\n scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-msg::placeholder{color:var(--reticle-faint);}\n[data-reticle-hud] .reticle-msg:disabled{opacity:.5;}\n[data-reticle-hud] .reticle-send{flex:none;width:30px;height:30px;padding:0;border-radius:10px;border:none;cursor:pointer;pointer-events:auto;\n background:var(--reticle-accent);color:#0b0d14;display:inline-flex;align-items:center;justify-content:center;transition:filter .15s,transform .1s;}\n[data-reticle-hud] .reticle-send svg{display:block;}\n[data-reticle-hud] .reticle-send:hover{filter:brightness(1.12);}\n[data-reticle-hud] .reticle-send:active{transform:scale(.9);}\n[data-reticle-hud] .reticle-send:disabled{opacity:.4;cursor:default;}\n[data-reticle-hud] .reticle-banner{display:none;flex:none;padding:8px 15px;color:var(--reticle-accent);\n font-size:11.5px;font-weight:500;border-bottom:1px solid var(--reticle-line2);background:var(--reticle-accent-soft);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-banner]{display:block;}\n/* Export row: hidden during a live session; revealed when ended so the run can be copied/saved. */\n[data-reticle-hud] .reticle-export{display:none;align-items:center;gap:8px;margin-top:9px;}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud] .reticle-export{display:flex;}\n[data-reticle-hud] .reticle-export-msg{color:var(--reticle-ok);font-size:11px;opacity:0;transition:opacity .15s;}\n[data-reticle-hud] .reticle-export-msg[data-show=\"1\"]{opacity:1;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 3px rgba(246,180,76,.9),inset 0 0 30px 6px rgba(246,180,76,.4);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(61,215,166,.55);}\n/* Handoff tones tell the human the agent's mode at a glance. waiting = calm teal \"your turn\" (no\n alarm); ask = amber \"answer me\" with a pulse; warn = amber \"agent crashed\" with a pulse. Each leads\n the banner with an icon and overrides the calm ended-green accent. */\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-hud]{--reticle-accent:#38bdf8;--reticle-accent-soft:rgba(56,189,248,.16);}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]{font-weight:600;color:#7dd3fc;}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]::before{content:\"\\270B \";}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(56,189,248,.5);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-hud],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-hud]{--reticle-accent:#fb923c;--reticle-accent-soft:rgba(251,146,60,.18);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]{font-weight:600;color:#fdba74;}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner]::before{content:\"\\2753 \";}\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]::before{content:\"\\26A0\\FE0F \";}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-glow][data-on=\"1\"],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-glow][data-on=\"1\"]{animation:reticle-warn-pulse 1.5s ease-in-out infinite;\n box-shadow:inset 0 0 0 2px rgba(251,146,60,.7);}\n@keyframes reticle-warn-pulse{0%,100%{box-shadow:inset 0 0 0 2px rgba(251,146,60,.32);}\n 50%{box-shadow:inset 0 0 0 3px rgba(251,146,60,.85),inset 0 0 26px 5px rgba(251,146,60,.34);}}\n/* Replay-a-flow row: the human re-runs a saved flow with no agent. Hidden until flows are pushed.\n Bounded + self-scrolling: it sits between the flex:1 log and the flex:none composer, so without a\n height cap a long flow list would squeeze the log to nothing and push the message input past the\n panel's overflow:hidden clip. flex:none + max-height + overflow-y keep the log and input always\n visible; extra flow chips scroll inside this section instead of growing the panel. */\n[data-reticle-hud] .reticle-flows{display:none;flex:none;flex-wrap:wrap;align-content:flex-start;gap:6px;\n padding:9px 12px;border-top:1px solid var(--reticle-line2);max-height:88px;overflow-y:auto;overscroll-behavior:contain;}\n[data-reticle-hud] .reticle-flows[data-has=\"1\"]{display:flex;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-flows-cap{flex:0 0 100%;margin-bottom:1px;color:var(--reticle-faint);font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;}\n[data-reticle-hud] .reticle-flow{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;\n border-radius:7px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);color:var(--reticle-muted);\n font-family:var(--reticle-font);font-size:11px;font-weight:500;transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-flow:hover{color:var(--reticle-fg);background:var(--reticle-accent-soft);border-color:var(--reticle-accent);}\n[data-reticle-hud] .reticle-flow:active{transform:scale(.95);}\n";
|
|
@@ -7,8 +7,8 @@ import { CONTROLS_CSS } from './presenter-controls.js';
|
|
|
7
7
|
*/
|
|
8
8
|
// No web-font @import: a dev-only SDK must not make the host page issue a cross-origin request to
|
|
9
9
|
// fonts.googleapis.com (and trip a `style-src 'self'` CSP with a console violation) just for HUD
|
|
10
|
-
// polish. The font
|
|
11
|
-
//
|
|
10
|
+
// polish. The font var below already prefers Inter if present and falls back to the system stack
|
|
11
|
+
// otherwise — so the overlay stays self-contained and never touches the network.
|
|
12
12
|
export const PRESENTER_CSS = `
|
|
13
13
|
[data-reticle-glow]{position:fixed;inset:0;pointer-events:none;z-index:2147483600;opacity:0;
|
|
14
14
|
transition:opacity .25s ease;box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 28px 6px rgba(99,102,241,.45);}
|
|
@@ -36,7 +36,6 @@ export const PRESENTER_CSS = `
|
|
|
36
36
|
--reticle-line:rgba(255,255,255,.09);--reticle-line2:rgba(255,255,255,.05);
|
|
37
37
|
--reticle-read:#54d2e6;--reticle-ok:#3dd7a6;--reticle-bad:#ff7a7a;
|
|
38
38
|
--reticle-font:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
|
39
|
-
--reticle-serif:"IBM Plex Serif",Georgia,"Times New Roman",serif;
|
|
40
39
|
position:fixed;left:50%;right:auto;bottom:20px;box-sizing:border-box;
|
|
41
40
|
width:384px;height:468px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);
|
|
42
41
|
display:flex;flex-direction:column;overflow:hidden;text-align:left;z-index:2147483647;pointer-events:none;
|
|
@@ -68,11 +67,15 @@ export const PRESENTER_CSS = `
|
|
|
68
67
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud][data-on="1"]{pointer-events:auto;}
|
|
69
68
|
[data-reticle-hud] .reticle-hud-head{display:flex;align-items:center;gap:8px;flex:none;
|
|
70
69
|
padding:12px 12px 12px 15px;border-bottom:1px solid var(--reticle-line2);}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
[data-reticle-hud] .reticle-
|
|
70
|
+
/* Brand block: both marks always in the DOM, CSS picks one off data-reticle-min (see below) so the
|
|
71
|
+
collapsed/expanded swap needs no JS state. Artwork paints with currentColor, so it inherits
|
|
72
|
+
--reticle-fg and reads correctly on any HUD surface instead of baking in the export's #FAFAFA. */
|
|
73
|
+
[data-reticle-hud] .reticle-brand{display:inline-flex;align-items:center;flex:none;color:var(--reticle-fg);}
|
|
74
|
+
[data-reticle-hud] .reticle-mark{display:none;height:18px;width:auto;flex:none;}
|
|
75
|
+
[data-reticle-hud] .reticle-wordmark{display:block;height:16px;width:auto;flex:none;}
|
|
76
|
+
/* Visually hidden, still announced — the marks are aria-hidden artwork, this names the HUD. */
|
|
77
|
+
[data-reticle-hud] .reticle-brand-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
|
|
78
|
+
overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;}
|
|
76
79
|
[data-reticle-hud] .reticle-head-sp{flex:1;}
|
|
77
80
|
[data-reticle-hud] .reticle-live{display:none;flex:1;min-width:0;color:var(--reticle-muted);font-size:12.5px;
|
|
78
81
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
@@ -103,7 +106,6 @@ export const PRESENTER_CSS = `
|
|
|
103
106
|
[data-reticle-hud] .reticle-chip[data-mode="idle"]{display:none;}
|
|
104
107
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud]{height:50px;border-radius:25px;cursor:pointer;}
|
|
105
108
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-hud-head{border-bottom:none;height:50px;padding:0 12px 0 16px;}
|
|
106
|
-
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-brand,
|
|
107
109
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-chip,
|
|
108
110
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-head-sp,
|
|
109
111
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] [data-reticle-min-btn],
|
|
@@ -113,6 +115,9 @@ export const PRESENTER_CSS = `
|
|
|
113
115
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] [data-reticle-log],
|
|
114
116
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] [data-reticle-foot],
|
|
115
117
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-banner{display:none;}
|
|
118
|
+
/* Collapsed to a pill: mark only, beside the dot. Expanded: the full wordmark. */
|
|
119
|
+
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-wordmark{display:none;}
|
|
120
|
+
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-mark{display:block;}
|
|
116
121
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-live{display:block;}
|
|
117
122
|
[data-reticle-overlay][data-reticle-min="1"] [data-reticle-hud] .reticle-maxhint{display:inline-flex;}
|
|
118
123
|
[data-reticle-mode="reading"] [data-reticle-glow][data-on="1"]{
|
|
@@ -122,5 +127,11 @@ export const PRESENTER_CSS = `
|
|
|
122
127
|
[data-reticle-overlay][data-reticle-throttled="1"] [data-reticle-glow][data-on="1"]{
|
|
123
128
|
box-shadow:inset 0 0 0 3px rgba(251,191,36,.9),inset 0 0 28px 6px rgba(251,191,36,.45);}
|
|
124
129
|
[data-reticle-overlay][data-reticle-throttled="1"] [data-reticle-hud]{--reticle-accent:#fbbf24;--reticle-accent-soft:rgba(251,191,36,.16);}
|
|
130
|
+
/* The two INFINITE animations — the header dot and the border glow — are pure decoration and were
|
|
131
|
+
blinking regardless of the user's motion preference. Everything else the presenter animates is a
|
|
132
|
+
one-shot (ripple, tally pop) or a transition, which reduce-motion tolerates. */
|
|
133
|
+
@media (prefers-reduced-motion:reduce){
|
|
134
|
+
[data-reticle-glow][data-on="1"],
|
|
135
|
+
[data-reticle-glow][data-on="1"][data-busy="1"]{animation:none;}}
|
|
125
136
|
${LOG_CSS}
|
|
126
137
|
${CONTROLS_CSS}`;
|
|
@@ -21,14 +21,14 @@ export function renderTally(el, runLog, prev) {
|
|
|
21
21
|
const next = countVerdicts(runLog);
|
|
22
22
|
if (el === undefined)
|
|
23
23
|
return next;
|
|
24
|
-
if (next.passes
|
|
24
|
+
if (0 === next.passes && 0 === next.fails) {
|
|
25
25
|
el.setAttribute('hidden', '');
|
|
26
26
|
return next;
|
|
27
27
|
}
|
|
28
28
|
const bumpPass = next.passes > prev.passes ? ' data-bump="1"' : '';
|
|
29
29
|
const bumpFail = next.fails > prev.fails ? ' data-bump="1"' : '';
|
|
30
|
-
const dimP = next.passes
|
|
31
|
-
const dimF = next.fails
|
|
30
|
+
const dimP = 0 === next.passes ? ' data-z="1"' : '';
|
|
31
|
+
const dimF = 0 === next.fails ? ' data-z="1"' : '';
|
|
32
32
|
el.removeAttribute('hidden');
|
|
33
33
|
el.innerHTML =
|
|
34
34
|
`<span class="reticle-t-pass"${dimP}${bumpPass}>✓ ${String(next.passes)}</span>` +
|
|
@@ -7,9 +7,10 @@ export declare const wait: (ms: number) => Promise<void>;
|
|
|
7
7
|
export declare const until: (pred: () => boolean, ms?: number) => Promise<boolean>;
|
|
8
8
|
export declare const dataOn: () => string | null;
|
|
9
9
|
export declare const dataBusy: () => string | null;
|
|
10
|
-
|
|
10
|
+
interface GlowFlips {
|
|
11
11
|
enters: number;
|
|
12
12
|
exits: number;
|
|
13
13
|
stop: () => void;
|
|
14
14
|
}
|
|
15
15
|
export declare function trackGlowFlips(glow: HTMLElement): GlowFlips;
|
|
16
|
+
export {};
|
|
@@ -22,9 +22,9 @@ export function trackGlowFlips(glow) {
|
|
|
22
22
|
if (r.attributeName !== 'data-on')
|
|
23
23
|
continue;
|
|
24
24
|
const v = glow.getAttribute('data-on');
|
|
25
|
-
if (
|
|
25
|
+
if ('1' === v)
|
|
26
26
|
counts.enters++;
|
|
27
|
-
if (
|
|
27
|
+
if ('0' === v)
|
|
28
28
|
counts.exits++;
|
|
29
29
|
}
|
|
30
30
|
});
|
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
// Present-tense verb for an action label in the presenter HUD (cursor status + act-log row).
|
|
2
2
|
// Presenter-only UI copy — never a wire string. Shared by presenter.ts (cursor) and reticle.ts
|
|
3
3
|
// (act-log row) so the two stay in lockstep.
|
|
4
|
+
//
|
|
5
|
+
// The CASES are wire values though, and they are core's ActionType. Spelled as free strings here,
|
|
6
|
+
// a rename in core left this switch silently falling through to `default` — which returns the raw
|
|
7
|
+
// action name, so the HUD degrades to "webmcp Save" instead of a verb and nothing fails.
|
|
8
|
+
import { ActionType } from '@reticlehq/core';
|
|
4
9
|
export function actionVerb(action) {
|
|
5
10
|
switch (action) {
|
|
6
|
-
case
|
|
7
|
-
case
|
|
11
|
+
case ActionType.CLICK:
|
|
12
|
+
case ActionType.DBLCLICK:
|
|
8
13
|
return 'Clicking';
|
|
9
|
-
case
|
|
10
|
-
case
|
|
14
|
+
case ActionType.FILL:
|
|
15
|
+
case ActionType.TYPE:
|
|
11
16
|
return 'Typing into';
|
|
12
|
-
case
|
|
17
|
+
case ActionType.HOVER:
|
|
13
18
|
return 'Hovering';
|
|
14
|
-
case
|
|
19
|
+
case ActionType.SELECT:
|
|
15
20
|
return 'Selecting';
|
|
16
|
-
case
|
|
21
|
+
case ActionType.SUBMIT:
|
|
17
22
|
return 'Submitting';
|
|
18
|
-
case
|
|
19
|
-
case
|
|
23
|
+
case ActionType.CHECK:
|
|
24
|
+
case ActionType.UNCHECK:
|
|
20
25
|
return 'Toggling';
|
|
21
|
-
case
|
|
26
|
+
case ActionType.UPLOAD:
|
|
22
27
|
return 'Uploading to';
|
|
23
|
-
case
|
|
28
|
+
case ActionType.DRAG:
|
|
24
29
|
return 'Dragging';
|
|
25
30
|
default:
|
|
26
31
|
return action;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
|
|
1
|
+
import { ActionType, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
|
|
2
2
|
import { refs } from '../dom/refs.js';
|
|
3
3
|
import { actionVerb } from './presenter-verbs.js';
|
|
4
4
|
import { nativeSetTimeout, nativeClearTimeout, nativeNow } from '../timers/native-timers.js';
|
|
5
5
|
import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, HUMAN_ROW_PREFIX, clampLogMax, formatElapsed, humanDuration, appendLogRow, } from './presenter-log.js';
|
|
6
6
|
import { PRESENTER_CSS } from './presenter-styles.js';
|
|
7
|
-
import {
|
|
7
|
+
import { BRAND_HTML } from './presenter-brand.js';
|
|
8
|
+
import { BorderMode, DEFAULT_BORDER_MODE, DATA_BUSY, BUSY_OFF, effectivePaceMs, GlowPhase, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, MIN_ATTR, THROTTLED_ATTR, } from './presenter-config.js';
|
|
8
9
|
import { buildRunState } from './presenter-run-state.js';
|
|
9
10
|
import { moveCursor, ringAround, spawnRipple, pace } from './presenter-effects.js';
|
|
10
11
|
import { GlowController } from './presenter-glow.js';
|
|
@@ -57,7 +58,7 @@ export class Presenter {
|
|
|
57
58
|
#onControl;
|
|
58
59
|
#panel;
|
|
59
60
|
constructor(options = {}) {
|
|
60
|
-
this.#paceMs = options.paceMs
|
|
61
|
+
this.#paceMs = effectivePaceMs(options.paceMs);
|
|
61
62
|
this.#now = options.now ?? nativeNow;
|
|
62
63
|
this.#heartbeatMs = options.heartbeatMs ?? HEARTBEAT_MS;
|
|
63
64
|
this.#idleNoticeMs = options.idleNoticeMs ?? IDLE_NOTICE_MS;
|
|
@@ -111,7 +112,7 @@ export class Presenter {
|
|
|
111
112
|
return void this.#panel.setFlows(a['flows']);
|
|
112
113
|
const state = a['state'];
|
|
113
114
|
const tone = a['tone'];
|
|
114
|
-
const text = typeof a['text']
|
|
115
|
+
const text = 'string' === typeof a['text'] && a['text'].length > 0 ? a['text'] : undefined;
|
|
115
116
|
if (isSessionState(state))
|
|
116
117
|
this.setState(state, text, isPresenterTone(tone) ? tone : undefined);
|
|
117
118
|
}
|
|
@@ -124,7 +125,7 @@ export class Presenter {
|
|
|
124
125
|
this.#pruneLog();
|
|
125
126
|
}
|
|
126
127
|
mount() {
|
|
127
|
-
if (this.#root !== undefined || typeof document
|
|
128
|
+
if (this.#root !== undefined || 'undefined' === typeof document)
|
|
128
129
|
return;
|
|
129
130
|
const style = document.createElement('style');
|
|
130
131
|
style.setAttribute('data-reticle-overlay', '');
|
|
@@ -137,7 +138,7 @@ export class Presenter {
|
|
|
137
138
|
<div data-reticle-cursor></div>
|
|
138
139
|
<div data-reticle-ring></div>
|
|
139
140
|
<div data-reticle-hud>
|
|
140
|
-
<div class="reticle-hud-head"
|
|
141
|
+
<div class="reticle-hud-head">${BRAND_HTML}<span class="reticle-chip" data-reticle-chip></span><span class="reticle-tally" data-reticle-tally hidden></span><span class="reticle-live"></span><span class="reticle-head-sp"></span><button type="button" data-reticle-min-btn title="Minimise" aria-label="Minimise the panel">⌄</button>${CONTROLS_HEAD_HTML}<span class="reticle-maxhint" aria-hidden="true">⌃</span></div>
|
|
141
142
|
<div class="reticle-act-strip"><span class="reticle-act">idle</span></div>
|
|
142
143
|
${CONTROLS_BANNER_HTML}
|
|
143
144
|
<div ${DATA_RETICLE_LOG}></div>
|
|
@@ -162,7 +163,7 @@ export class Presenter {
|
|
|
162
163
|
setMin(true);
|
|
163
164
|
});
|
|
164
165
|
root.querySelector('.reticle-hud-head')?.addEventListener('click', () => {
|
|
165
|
-
if (root.getAttribute(MIN_ATTR)
|
|
166
|
+
if ('1' === root.getAttribute(MIN_ATTR))
|
|
166
167
|
setMin(false); // clicking the minimised bar restores
|
|
167
168
|
});
|
|
168
169
|
this.#glowCtl.setElements(this.#glow, this.#cursor);
|
|
@@ -348,7 +349,7 @@ export class Presenter {
|
|
|
348
349
|
if (this.#log === undefined)
|
|
349
350
|
return undefined;
|
|
350
351
|
const trimmed = text.trim();
|
|
351
|
-
if (trimmed.length
|
|
352
|
+
if (0 === trimmed.length)
|
|
352
353
|
return undefined;
|
|
353
354
|
this.#logBaseMs ??= ms;
|
|
354
355
|
// Structured run-log entry (mirrors the DOM row) for the exported run state, capped like the DOM.
|
|
@@ -381,7 +382,7 @@ export class Presenter {
|
|
|
381
382
|
}
|
|
382
383
|
/** Back-compat: narration appends to the live log (append-only, never overwrites). */
|
|
383
384
|
narrate(text, level = 'info') {
|
|
384
|
-
const line =
|
|
385
|
+
const line = 'info' === level ? text : `[${level}] ${text}`;
|
|
385
386
|
return this.log(LOG_KIND.NARRATION, line);
|
|
386
387
|
}
|
|
387
388
|
#pruneLog() {
|
|
@@ -417,7 +418,9 @@ export class Presenter {
|
|
|
417
418
|
moveCursor(this.#cursor, cx, cy);
|
|
418
419
|
ringAround(this.#ring, rect);
|
|
419
420
|
await pace(this.#paceMs);
|
|
420
|
-
if (
|
|
421
|
+
if (ActionType.CLICK === action ||
|
|
422
|
+
ActionType.DBLCLICK === action ||
|
|
423
|
+
ActionType.SUBMIT === action)
|
|
421
424
|
spawnRipple(this.#root, cx, cy);
|
|
422
425
|
}
|
|
423
426
|
}
|
|
@@ -33,6 +33,7 @@ const ANNOTATION_LABEL = {
|
|
|
33
33
|
[AnnotationKind.ASSERT_SIGNAL]: 'assert signal',
|
|
34
34
|
[AnnotationKind.ASSERT_VISIBLE]: 'assert visible',
|
|
35
35
|
[AnnotationKind.ASSERT_STATE]: 'assert state',
|
|
36
|
+
[AnnotationKind.ASSERT_NET]: 'assert request',
|
|
36
37
|
[AnnotationKind.MARK_DYNAMIC]: 'mark dynamic',
|
|
37
38
|
[AnnotationKind.SUCCESS_STATE]: 'success state',
|
|
38
39
|
[AnnotationKind.INTENT]: 'intent',
|
|
@@ -64,13 +65,13 @@ export function anchorFor(el) {
|
|
|
64
65
|
function isTextbox(el) {
|
|
65
66
|
if (el instanceof HTMLTextAreaElement)
|
|
66
67
|
return true;
|
|
67
|
-
return el instanceof HTMLInputElement && inputRole(el)
|
|
68
|
+
return el instanceof HTMLInputElement && 'textbox' === inputRole(el);
|
|
68
69
|
}
|
|
69
70
|
function inputRole(el) {
|
|
70
71
|
const type = el.type.toLowerCase();
|
|
71
|
-
if (
|
|
72
|
+
if ('checkbox' === type)
|
|
72
73
|
return 'checkbox';
|
|
73
|
-
if (
|
|
74
|
+
if ('radio' === type)
|
|
74
75
|
return 'radio';
|
|
75
76
|
if (['text', 'email', 'tel', 'url', 'search', 'password', ''].includes(type))
|
|
76
77
|
return 'textbox';
|
|
@@ -163,7 +164,7 @@ class Recorder {
|
|
|
163
164
|
mount() {
|
|
164
165
|
if (this.#root !== undefined)
|
|
165
166
|
return;
|
|
166
|
-
if (typeof document
|
|
167
|
+
if ('undefined' === typeof document)
|
|
167
168
|
return;
|
|
168
169
|
this.#buildToolbar();
|
|
169
170
|
this.#installCapture();
|
|
@@ -222,7 +223,7 @@ class Recorder {
|
|
|
222
223
|
// semantic step (CHECK/UNCHECK/SELECT). Recording a CLICK here too would replay any onChange
|
|
223
224
|
// side effect twice (analytics, dependent-field resets), so skip the duplicate.
|
|
224
225
|
if ((target instanceof HTMLInputElement &&
|
|
225
|
-
(
|
|
226
|
+
('checkbox' === inputRole(target) || 'radio' === inputRole(target))) ||
|
|
226
227
|
target instanceof HTMLSelectElement) {
|
|
227
228
|
return;
|
|
228
229
|
}
|
|
@@ -244,12 +245,12 @@ class Recorder {
|
|
|
244
245
|
const target = this.#ignore(ev);
|
|
245
246
|
if (target === undefined)
|
|
246
247
|
return;
|
|
247
|
-
if (target instanceof HTMLInputElement && inputRole(target)
|
|
248
|
+
if (target instanceof HTMLInputElement && 'checkbox' === inputRole(target)) {
|
|
248
249
|
this.#flushPendingFill();
|
|
249
250
|
this.#steps.push(buildStep(target, target.checked ? ActionType.CHECK : ActionType.UNCHECK));
|
|
250
251
|
return;
|
|
251
252
|
}
|
|
252
|
-
if (target instanceof HTMLInputElement && inputRole(target)
|
|
253
|
+
if (target instanceof HTMLInputElement && 'radio' === inputRole(target)) {
|
|
253
254
|
this.#flushPendingFill();
|
|
254
255
|
this.#steps.push(buildStep(target, ActionType.CHECK));
|
|
255
256
|
return;
|
|
@@ -328,7 +329,7 @@ class Recorder {
|
|
|
328
329
|
#start() {
|
|
329
330
|
// A fresh span — no leakage from a previous Record→Stop cycle.
|
|
330
331
|
this.#steps = [];
|
|
331
|
-
this.#startPath = typeof location
|
|
332
|
+
this.#startPath = 'undefined' === typeof location ? undefined : location.pathname;
|
|
332
333
|
this.#annotations = [];
|
|
333
334
|
this.#pendingFill = undefined;
|
|
334
335
|
this.#draft = undefined;
|
|
@@ -340,7 +341,7 @@ class Recorder {
|
|
|
340
341
|
const name = this.#nameField() ?? this.#deps.defaultName ?? DEFAULT_NAME;
|
|
341
342
|
const flow = compileRecording(name, this.#steps, this.#annotations, this.#deps.now(), this.#startPath);
|
|
342
343
|
this.#deps.emit(EventType.FLOW_RECORDED, { name, flow });
|
|
343
|
-
this.#setStatus(this.#steps.length
|
|
344
|
+
this.#setStatus(0 === this.#steps.length ? RECORDER_EMPTY_MSG : STATUS_IDLE);
|
|
344
345
|
this.#setPhase(RecorderPhase.IDLE);
|
|
345
346
|
this.#closeMenu();
|
|
346
347
|
}
|
|
@@ -16,6 +16,8 @@ export interface CapabilitiesInput {
|
|
|
16
16
|
stores?: string[];
|
|
17
17
|
flows?: CapabilityFlow[];
|
|
18
18
|
}
|
|
19
|
+
/** Set by the SDK at connect. Idempotent; the last connect wins. */
|
|
20
|
+
export declare function setCapabilitiesListener(cb: (() => void) | undefined): void;
|
|
19
21
|
/** Called by the host app via reticle.describe. Merges (idempotent), never replaces wholesale. */
|
|
20
22
|
export declare function registerCapabilities(input: CapabilitiesInput): void;
|
|
21
23
|
/** Snapshot copy of the registered capabilities (defensive — never hand out the live arrays). */
|
|
@@ -12,6 +12,21 @@ function mergeUnique(into, add) {
|
|
|
12
12
|
if (!into.includes(v))
|
|
13
13
|
into.push(v);
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Notified whenever capabilities change, so the SDK can re-announce them to the bridge.
|
|
17
|
+
*
|
|
18
|
+
* `hasCapabilities` rides in the HELLO, which goes out at connect() — and registering deliberately
|
|
19
|
+
* happens AFTER connect, because `registerStore` needs a live SDK to subscribe through. Without a
|
|
20
|
+
* notification, an app that declared its whole testable surface still appeared to the agent as
|
|
21
|
+
* having none, permanently. The hook lives HERE rather than on `reticle.describe` because the
|
|
22
|
+
* documented entry point is this bare function; only wiring `describe` would have fixed the path
|
|
23
|
+
* almost nobody uses.
|
|
24
|
+
*/
|
|
25
|
+
let onChanged;
|
|
26
|
+
/** Set by the SDK at connect. Idempotent; the last connect wins. */
|
|
27
|
+
export function setCapabilitiesListener(cb) {
|
|
28
|
+
onChanged = cb;
|
|
29
|
+
}
|
|
15
30
|
/** Called by the host app via reticle.describe. Merges (idempotent), never replaces wholesale. */
|
|
16
31
|
export function registerCapabilities(input) {
|
|
17
32
|
mergeUnique(capabilities.testids, input.testids);
|
|
@@ -28,6 +43,7 @@ export function registerCapabilities(input) {
|
|
|
28
43
|
}
|
|
29
44
|
}
|
|
30
45
|
}
|
|
46
|
+
onChanged?.();
|
|
31
47
|
}
|
|
32
48
|
/** Snapshot copy of the registered capabilities (defensive — never hand out the live arrays). */
|
|
33
49
|
export function getCapabilities() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StoreLike } from './stores.js';
|
|
1
|
+
import type { StoreLike, StoreSubscribe } from './stores.js';
|
|
2
2
|
/**
|
|
3
3
|
* Adapters that give non-`{getState, subscribe}` state libraries the shape `registerStore` wants.
|
|
4
4
|
*
|
|
@@ -119,6 +119,132 @@ export declare function valtioStore<T extends object>(proxy: T, snapshot: (p: T)
|
|
|
119
119
|
* ```
|
|
120
120
|
*/
|
|
121
121
|
export declare function mobxStore<T>(observable: T, toJS: (value: T) => unknown, reaction: (track: () => unknown, effect: () => void) => () => void): StoreLike;
|
|
122
|
+
/** The minimum of a Recoil `Loadable` this adapter touches. */
|
|
123
|
+
interface RecoilLoadableLike {
|
|
124
|
+
/** 'hasValue' | 'loading' | 'hasError'. Widened to string: it arrives from outside this package. */
|
|
125
|
+
state: string;
|
|
126
|
+
/** The value, the pending promise, or the thrown error — which one depends on `state`. */
|
|
127
|
+
contents: unknown;
|
|
128
|
+
}
|
|
129
|
+
/** The minimum of a Recoil `Snapshot` this adapter touches, generic in the ATOM type. */
|
|
130
|
+
interface RecoilSnapshotLike<A> {
|
|
131
|
+
getLoadable: (atom: A) => RecoilLoadableLike;
|
|
132
|
+
}
|
|
133
|
+
/** How one Recoil atom is projected: the value never travels without the load state beside it. */
|
|
134
|
+
export interface RecoilAtomSnapshot {
|
|
135
|
+
status: string;
|
|
136
|
+
value: unknown;
|
|
137
|
+
error: string | null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Expose a chosen set of Recoil atoms as one Reticle store.
|
|
141
|
+
*
|
|
142
|
+
* Three things about Recoil's API shape this adapter is built around, none of them optional:
|
|
143
|
+
*
|
|
144
|
+
* - **The atom map is forced, not an ergonomic choice.** Recoil has no enumerable registry of live
|
|
145
|
+
* atoms, so "the whole store" is not a thing that exists. Naming the atoms is the only way to
|
|
146
|
+
* snapshot them, and it doubles as a declaration of what matters — the same reason `jotaiStore`
|
|
147
|
+
* takes one.
|
|
148
|
+
* - **`.state`/`.contents`, never `.getValue()`.** On a pending async selector `getValue()` THROWS
|
|
149
|
+
* the pending promise, and on a failed one it throws the error. Either would take down the whole
|
|
150
|
+
* state read over one slow atom — so a loading atom reports `status: 'loading'` with a null value
|
|
151
|
+
* rather than silently becoming an empty object or an exception.
|
|
152
|
+
* - **One subscription, not one per atom.** Recoil exposes no per-atom subscription outside React
|
|
153
|
+
* (`jotaiStore` gets `store.sub(atom, …)`; there is no equivalent here). The transaction stream is
|
|
154
|
+
* the only public change signal, so the caller passes it in — the same "the library ships free
|
|
155
|
+
* functions, so hand them over" shape as `valtioStore` and `mobxStore`.
|
|
156
|
+
*
|
|
157
|
+
* Wire it from a bridge component, which is where `useRecoilTransactionObserver_UNSTABLE` lives:
|
|
158
|
+
*
|
|
159
|
+
* ```tsx
|
|
160
|
+
* const latest = useRef(snapshot_UNSTABLE());
|
|
161
|
+
* const listeners = useRef(new Set<() => void>()).current;
|
|
162
|
+
* useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
|
|
163
|
+
* latest.current = snapshot;
|
|
164
|
+
* for (const l of listeners) l();
|
|
165
|
+
* });
|
|
166
|
+
* registerStore('recoil', recoilStore({ cart: cartAtom }, () => latest.current, (l) => {
|
|
167
|
+
* listeners.add(l);
|
|
168
|
+
* return () => listeners.delete(l);
|
|
169
|
+
* }));
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export declare function recoilStore<A>(atoms: Record<string, A>, snapshot: () => RecoilSnapshotLike<A>, subscribe: StoreSubscribe): StoreLike;
|
|
173
|
+
/**
|
|
174
|
+
* The minimum of a Svelte store this adapter touches.
|
|
175
|
+
*
|
|
176
|
+
* `subscribe` may return either the unsubscribe function or an object carrying one — the store
|
|
177
|
+
* contract permits both, and an RxJS-shaped source returns the object form.
|
|
178
|
+
*/
|
|
179
|
+
interface SvelteReadable {
|
|
180
|
+
subscribe: (run: (value: unknown) => void) => (() => void) | {
|
|
181
|
+
unsubscribe: () => void;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Expose a Svelte store as a Reticle store.
|
|
186
|
+
*
|
|
187
|
+
* Structurally unlike every other adapter in this file: they all wrap something that can be PULLED
|
|
188
|
+
* (`getState`, `getSnapshot`, `snapshot(proxy)`, `toJS`). A Svelte store has no pull side at all —
|
|
189
|
+
* `{ subscribe }` is the entire contract. What makes a pull possible anyway is the part of that
|
|
190
|
+
* contract which says `subscribe` must call back **immediately and synchronously** with the current
|
|
191
|
+
* value: subscribe, catch the value, unsubscribe, return it. That is precisely how `svelte/store`'s
|
|
192
|
+
* own `get()` is implemented, so it is a blessed read rather than a trick.
|
|
193
|
+
*
|
|
194
|
+
* Doing it that way is what keeps this adapter free of the `dispose` the obvious implementation
|
|
195
|
+
* needs. Caching the latest value would mean holding a permanent subscription, which means a
|
|
196
|
+
* teardown the other five adapters don't have, which means a leak the moment a caller forgets it.
|
|
197
|
+
* A transient subscription per read owns nothing and leaks nothing.
|
|
198
|
+
*
|
|
199
|
+
* ```ts
|
|
200
|
+
* registerStore('cart', svelteStore(cartStore));
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export declare function svelteStore(readable: SvelteReadable, warn?: (message: string) => void): StoreLike;
|
|
204
|
+
/**
|
|
205
|
+
* The minimum of a Pinia store this adapter touches.
|
|
206
|
+
*
|
|
207
|
+
* Two things here look over-specified and are not, both found by compiling against the real library
|
|
208
|
+
* rather than against a fake written to the same belief as the adapter:
|
|
209
|
+
*
|
|
210
|
+
* - **`$state: object`, not `Record<string, unknown>`.** A Pinia store's `$state` is a concrete
|
|
211
|
+
* object type with named keys and no index signature, and TypeScript rejects those for a
|
|
212
|
+
* `Record<string, unknown>` parameter. `piniaStore(useCartStore())` did not compile — the same
|
|
213
|
+
* shape of failure as the Jotai contravariance note above, and equally invisible to a fake.
|
|
214
|
+
* - **`flush` as the literal union**, not `string`. The option object is checked contravariantly, so
|
|
215
|
+
* a widened `string` here makes the real `$subscribe` unassignable to this shape and the whole
|
|
216
|
+
* store stops matching.
|
|
217
|
+
*/
|
|
218
|
+
interface PiniaStoreLike {
|
|
219
|
+
$state: object;
|
|
220
|
+
$subscribe: (callback: (mutation: unknown, state: unknown) => void, options?: {
|
|
221
|
+
detached?: boolean;
|
|
222
|
+
flush?: 'pre' | 'post' | 'sync';
|
|
223
|
+
}) => () => void;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Expose a Pinia store as a Reticle store. `$state` / `$subscribe` are close to `{getState,
|
|
227
|
+
* subscribe}` but not close enough for `registerStore`'s duck-type, which is the whole reason a Vue
|
|
228
|
+
* app currently connects and then reports no state at all.
|
|
229
|
+
*
|
|
230
|
+
* Both `$subscribe` options are load-bearing:
|
|
231
|
+
*
|
|
232
|
+
* - **`detached`** keeps the subscription alive past the component that happened to register the
|
|
233
|
+
* store. Without it Pinia tears the listener down on unmount, so a store registered from inside a
|
|
234
|
+
* component goes permanently silent after the first route change — readable, but never again
|
|
235
|
+
* emitting a STATE_CHANGE, which reads exactly like an app that stopped changing.
|
|
236
|
+
* - **`flush: 'sync'`** puts the notification inside the action's attribution window. The default
|
|
237
|
+
* (`'post'`) defers it past the Vue update tick, and a state change that lands after the window
|
|
238
|
+
* closed is no longer linked to the click that caused it in the causal summary.
|
|
239
|
+
*
|
|
240
|
+
* `$state` carries state, not getters — a Pinia getter is derived, so asserting on the state it
|
|
241
|
+
* derives from is the stronger assertion anyway.
|
|
242
|
+
*
|
|
243
|
+
* ```ts
|
|
244
|
+
* registerStore('cart', piniaStore(useCartStore()));
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export declare function piniaStore(store: PiniaStoreLike): StoreLike;
|
|
122
248
|
/**
|
|
123
249
|
* Build a store whose value is PUSHED in rather than pulled — for state that has no object to read.
|
|
124
250
|
*
|