@reticlehq/browser 2.13.1 → 3.1.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.d.ts +8 -20
- package/dist/actions/actions-dom.js +70 -69
- package/dist/actions/actions-press.d.ts +14 -0
- package/dist/actions/actions-press.js +123 -0
- package/dist/actions/actions.d.ts +1 -89
- package/dist/actions/actions.js +64 -365
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +6 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +8 -10
- package/dist/actions/scroll.js +28 -17
- package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
- package/dist/actions/synthetic/synthetic-input.js +13 -0
- package/dist/actions/value-input.d.ts +0 -30
- package/dist/actions/value-input.js +0 -33
- package/dist/commands/commands.d.ts +0 -2
- package/dist/commands/commands.js +10 -70
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +3 -90
- package/dist/dom/addressing/auto-anchor.d.ts +25 -0
- package/dist/dom/addressing/auto-anchor.js +41 -0
- package/dist/dom/addressing/refs.d.ts +14 -0
- package/dist/dom/addressing/refs.js +114 -0
- package/dist/dom/addressing/source.d.ts +9 -0
- package/dist/dom/addressing/source.js +29 -0
- package/dist/dom/chart.d.ts +0 -61
- package/dist/dom/chart.js +0 -60
- package/dist/dom/desktop-capture.d.ts +0 -30
- package/dist/dom/desktop-capture.js +1 -73
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +1 -48
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +31 -12
- package/dist/dom/paint-context.d.ts +6 -0
- package/dist/dom/paint-context.js +38 -0
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +50 -216
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- package/dist/dom/shadow-registry.d.ts +0 -33
- package/dist/dom/shadow-registry.js +2 -72
- package/dist/dom/snapshot.d.ts +2 -43
- package/dist/dom/snapshot.js +27 -152
- package/dist/dom/theme.d.ts +0 -37
- package/dist/dom/theme.js +2 -52
- package/dist/dom/virtualized.d.ts +0 -17
- package/dist/dom/virtualized.js +0 -27
- package/dist/edit-epoch.d.ts +1 -23
- package/dist/edit-epoch.js +1 -53
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -28
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +1 -4
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +3 -62
- package/dist/observers/console.d.ts +1 -2
- package/dist/observers/console.js +41 -30
- package/dist/observers/context-open.d.ts +1 -11
- package/dist/observers/context-open.js +10 -14
- package/dist/observers/departure.d.ts +3 -0
- package/dist/observers/departure.js +52 -0
- package/dist/observers/dialogs.d.ts +2 -0
- package/dist/observers/dialogs.js +39 -0
- package/dist/observers/dom.js +28 -52
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +39 -56
- package/dist/observers/focus.d.ts +0 -6
- package/dist/observers/focus.js +0 -7
- package/dist/observers/frames.d.ts +0 -9
- package/dist/observers/frames.js +1 -28
- package/dist/observers/health.d.ts +0 -5
- package/dist/observers/health.js +2 -34
- package/dist/observers/install-all.d.ts +2 -0
- package/dist/observers/install-all.js +26 -27
- package/dist/observers/ipc.d.ts +0 -25
- package/dist/observers/ipc.js +0 -65
- package/dist/observers/navigation.d.ts +2 -0
- package/dist/observers/navigation.js +33 -0
- package/dist/observers/net-detail/network-body.d.ts +12 -0
- package/dist/observers/net-detail/network-body.js +71 -0
- package/dist/observers/net-detail/network-redact.d.ts +1 -0
- package/dist/observers/net-detail/network-redact.js +1 -0
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +3 -46
- package/dist/observers/network.d.ts +3 -27
- package/dist/observers/network.js +178 -295
- package/dist/observers/perf.d.ts +0 -10
- package/dist/observers/perf.js +0 -16
- package/dist/observers/route.d.ts +1 -2
- package/dist/observers/route.js +13 -19
- package/dist/observers/scroll.d.ts +0 -1
- package/dist/observers/scroll.js +2 -11
- package/dist/observers/sdk-failure.d.ts +1 -9
- package/dist/observers/sdk-failure.js +1 -30
- package/dist/observers/state.d.ts +1 -11
- package/dist/observers/state.js +13 -41
- package/dist/observers/storage.d.ts +0 -14
- package/dist/observers/storage.js +10 -34
- package/dist/observers/types.d.ts +0 -15
- package/dist/observers/types.js +0 -14
- package/dist/patching/capture-method.d.ts +0 -24
- package/dist/patching/capture-method.js +0 -26
- package/dist/presenter/chrome/layers.d.ts +2 -0
- package/dist/presenter/chrome/layers.js +2 -0
- package/dist/presenter/chrome/log-kinds.d.ts +12 -0
- package/dist/presenter/chrome/log-kinds.js +7 -0
- package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
- package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
- package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
- package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
- package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
- package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
- package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
- package/dist/presenter/chrome/presenter-log.d.ts +17 -0
- package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
- package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
- package/dist/presenter/chrome/presenter-report-copy.js +95 -0
- package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
- package/dist/presenter/chrome/presenter-safe-html.js +16 -0
- package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
- package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
- package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
- package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
- package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
- package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
- package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
- package/dist/presenter/presenter-account-styles.d.ts +1 -0
- package/dist/presenter/presenter-account-styles.js +99 -0
- package/dist/presenter/presenter-account.d.ts +40 -0
- package/dist/presenter/presenter-account.js +178 -0
- package/dist/presenter/presenter-config.d.ts +10 -107
- package/dist/presenter/presenter-config.js +5 -92
- package/dist/presenter/presenter-controls.d.ts +1 -35
- package/dist/presenter/presenter-controls.js +10 -133
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +1 -5
- package/dist/presenter/presenter-drag.d.ts +2 -42
- package/dist/presenter/presenter-drag.js +3 -76
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +2 -28
- package/dist/presenter/presenter-hud-position.d.ts +18 -0
- package/dist/presenter/presenter-hud-position.js +50 -0
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +22 -7
- package/dist/presenter/presenter-report.d.ts +5 -7
- package/dist/presenter/presenter-report.js +44 -95
- package/dist/presenter/presenter-run-state.d.ts +1 -8
- package/dist/presenter/presenter-run-state.js +1 -6
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +2 -2
- package/dist/presenter/presenter-settings.d.ts +5 -23
- package/dist/presenter/presenter-settings.js +21 -33
- package/dist/presenter/presenter-shell-styles.js +23 -20
- package/dist/presenter/presenter-shell.d.ts +6 -18
- package/dist/presenter/presenter-shell.js +26 -77
- package/dist/presenter/presenter-styles.d.ts +2 -10
- package/dist/presenter/presenter-styles.js +6 -12
- package/dist/presenter/presenter-workspace.d.ts +4 -4
- package/dist/presenter/presenter-workspace.js +30 -10
- package/dist/presenter/presenter.d.ts +3 -63
- package/dist/presenter/presenter.js +29 -132
- package/dist/presenter/tour/tour-view.d.ts +33 -0
- package/dist/presenter/tour/tour-view.js +188 -0
- package/dist/presenter/tour/tour.d.ts +25 -0
- package/dist/presenter/tour/tour.js +245 -0
- package/dist/recorder/recorder-styles.d.ts +0 -6
- package/dist/recorder/recorder-styles.js +0 -6
- package/dist/recorder/recorder.d.ts +0 -34
- package/dist/recorder/recorder.js +6 -49
- package/dist/registry/auto-testids.d.ts +0 -19
- package/dist/registry/auto-testids.js +1 -21
- package/dist/registry/capabilities.d.ts +0 -31
- package/dist/registry/capabilities.js +1 -46
- package/dist/registry/commit-and-signal.d.ts +0 -6
- package/dist/registry/commit-and-signal.js +1 -7
- package/dist/registry/declared-channels.d.ts +2 -0
- package/dist/registry/declared-channels.js +14 -0
- package/dist/registry/declared-platform.d.ts +4 -0
- package/dist/registry/declared-platform.js +34 -0
- package/dist/registry/domains.d.ts +0 -8
- package/dist/registry/domains.js +0 -7
- package/dist/registry/emitter.d.ts +0 -14
- package/dist/registry/emitter.js +1 -11
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +1 -164
- package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
- package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +1 -60
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +10 -20
- package/dist/reticle-singleton.d.ts +2 -0
- package/dist/reticle-singleton.js +3 -0
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +115 -180
- package/dist/review/annotator-styles.js +1 -8
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +2 -42
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +3 -11
- package/dist/security/serialization.d.ts +0 -20
- package/dist/security/serialization.js +1 -66
- package/dist/session-continuity.d.ts +0 -16
- package/dist/session-continuity.js +1 -20
- package/dist/timers/clock.d.ts +1 -14
- package/dist/timers/clock.js +59 -60
- package/dist/timers/console/native-console.js +5 -0
- package/dist/timers/native/native-timers.d.ts +11 -0
- package/dist/timers/native/native-timers.js +48 -0
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +5 -131
- package/dist/transport/unreachable-message.d.ts +0 -17
- package/dist/transport/unreachable-message.js +0 -17
- package/dist/util/captured-method.d.ts +0 -35
- package/dist/util/captured-method.js +0 -35
- package/package.json +5 -4
- package/dist/actions/synthetic-input.d.ts +0 -9
- package/dist/actions/synthetic-input.js +0 -40
- package/dist/dom/auto-anchor.d.ts +0 -65
- package/dist/dom/auto-anchor.js +0 -74
- package/dist/dom/refs.d.ts +0 -90
- package/dist/dom/refs.js +0 -245
- package/dist/dom/source.d.ts +0 -53
- package/dist/dom/source.js +0 -74
- package/dist/observers/network-body.d.ts +0 -35
- package/dist/observers/network-body.js +0 -168
- package/dist/observers/network-redact.d.ts +0 -20
- package/dist/observers/network-redact.js +0 -80
- package/dist/presenter/presenter-brand.d.ts +0 -22
- package/dist/presenter/presenter-effects.d.ts +0 -13
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
- package/dist/presenter/presenter-log.d.ts +0 -64
- package/dist/presenter/presenter-report-copy.d.ts +0 -112
- package/dist/presenter/presenter-report-copy.js +0 -156
- package/dist/presenter/presenter-test-helpers.d.ts +0 -16
- package/dist/presenter/presenter-test-helpers.js +0 -41
- package/dist/timers/native-console.js +0 -9
- package/dist/timers/native-timers.d.ts +0 -36
- package/dist/timers/native-timers.js +0 -78
- /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
- /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import { ActionType, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
|
|
2
|
-
import { refs } from '../dom/refs.js';
|
|
1
|
+
import { ActionType, HumanControlKind, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
|
|
2
|
+
import { refs } from '../dom/addressing/refs.js';
|
|
3
3
|
import { unreachableStripText } from '../transport/unreachable-message.js';
|
|
4
|
-
import { actionVerb } from './presenter-verbs.js';
|
|
5
|
-
import { nativeSetTimeout, nativeClearTimeout, nativeNow } from '../timers/native-timers.js';
|
|
6
|
-
import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, humanDuration, appendLogRow, } from './presenter-log.js';
|
|
4
|
+
import { actionVerb } from './chrome/presenter-verbs.js';
|
|
5
|
+
import { nativeSetTimeout, nativeClearTimeout, nativeNow } from '../timers/native/native-timers.js';
|
|
6
|
+
import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, humanDuration, appendLogRow, } from './chrome/presenter-log.js';
|
|
7
7
|
import { PRESENTER_CSS } from './presenter-styles.js';
|
|
8
8
|
import { HudShell } from './presenter-shell.js';
|
|
9
|
-
import { parseImpactSnapshot } from './presenter-report-copy.js';
|
|
9
|
+
import { parseImpactSnapshot } from './chrome/presenter-report-copy.js';
|
|
10
10
|
import { BorderMode, DEFAULT_BORDER_MODE, DATA_BUSY, BUSY_OFF, effectivePaceMs, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, ACT_STRIP, STATE_ATTR, UNREACHABLE_STATE, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, THROTTLED_ATTR, LIVENESS_ATTR, MARKERS_BTN_ATTR, CLEAR_MARKS_ATTR, MARK_COUNT_ATTR, } from './presenter-config.js';
|
|
11
11
|
import { buildRunState } from './presenter-run-state.js';
|
|
12
|
-
import { moveCursor, ringAround, spawnRipple, pace } from './presenter-effects.js';
|
|
12
|
+
import { moveCursor, ringAround, spawnRipple, pace } from './chrome/presenter-effects.js';
|
|
13
13
|
import { GlowController } from './presenter-glow.js';
|
|
14
|
-
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from './presenter-icons.js';
|
|
15
|
-
import { renderTally } from './presenter-tally.js';
|
|
14
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from './icons/presenter-icons.js';
|
|
15
|
+
import { renderTally } from './chrome/presenter-tally.js';
|
|
16
16
|
import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML, ENDED_FADE_MS, ControlPanel, } from './presenter-controls.js';
|
|
17
17
|
import { statusTheme, blockerHtml, getPresenterSettings, OutputDetail, syncPageBlocker, } from './presenter-settings.js';
|
|
18
18
|
import { Annotator } from '../review/annotator.js';
|
|
19
|
-
// Re-export the config surface so the public import path (`./presenter.js`) is unchanged.
|
|
20
19
|
export { GlowPhase } from './presenter-config.js';
|
|
21
|
-
export { LOG_KIND, LOG_RESULT } from './presenter-log.js';
|
|
22
|
-
// Presenter / transparency layer: a human watches the agent work. Glowing border while
|
|
23
|
-
// active, a synthetic cursor that flies to targets, click/hover/type effects, and a HUD that
|
|
24
|
-
// shows the current action + the agent's narrated intent. All nodes carry data-reticle-* attrs
|
|
25
|
-
// so they're excluded from snapshots/observers (see dom-ignore.ts).
|
|
20
|
+
export { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
|
|
26
21
|
export class Presenter {
|
|
27
22
|
#paceMs;
|
|
28
23
|
#root;
|
|
@@ -32,12 +27,9 @@ export class Presenter {
|
|
|
32
27
|
#hud;
|
|
33
28
|
#actLine;
|
|
34
29
|
#actStrip;
|
|
35
|
-
/** Label inside the minimised-chat capsule; mirrors the act strip. */
|
|
36
30
|
#chatPillText;
|
|
37
|
-
/** Age of the last action, shown in the capsule's own slot. */
|
|
38
31
|
#chatPillTime;
|
|
39
32
|
#chip;
|
|
40
|
-
/** Live verdict tally (✓N ✗M) in the header - the running testing score the human watches. */
|
|
41
33
|
#tally;
|
|
42
34
|
#tallied = { passes: 0, fails: 0 };
|
|
43
35
|
#mode = PresenterMode.IDLE;
|
|
@@ -45,25 +37,18 @@ export class Presenter {
|
|
|
45
37
|
#heartbeatMs;
|
|
46
38
|
#idleNoticeMs;
|
|
47
39
|
#borderMode;
|
|
48
|
-
/** The glow / activity state machine (border shimmer + cursor visibility from activity timing). */
|
|
49
40
|
#glowCtl;
|
|
50
|
-
/** Liveness: the most recent action text + a 1s ticker that ages it into an "idle · {dur}" clock. */
|
|
51
41
|
#lastActionText = '';
|
|
52
42
|
#heartbeatTimer;
|
|
53
|
-
/** Session lifecycle: idle-end window (tweakable), session id, start/end cursors, structured run log. */
|
|
54
43
|
#idleEndMs;
|
|
55
44
|
#sessionId;
|
|
56
45
|
#startMs;
|
|
57
46
|
#endMs;
|
|
58
47
|
#runLog = [];
|
|
59
|
-
/** Tracks sessionStart/sessionEnd so both are idempotent (no strobe / no spurious off-write). */
|
|
60
48
|
#sessionActive = false;
|
|
61
|
-
// v2: narration + action status accumulate in a persistent, timestamped, scrollable log.
|
|
62
49
|
#logMax;
|
|
63
50
|
#log;
|
|
64
|
-
/** now of the first row, the baseline for the +elapsed timestamps. */
|
|
65
51
|
#logBaseMs;
|
|
66
|
-
// Live-control panel: the two-way control surface (Pause/Resume + End + message Send).
|
|
67
52
|
#onControl;
|
|
68
53
|
#panel;
|
|
69
54
|
#shell;
|
|
@@ -100,6 +85,7 @@ export class Presenter {
|
|
|
100
85
|
onExpand: () => this.#syncAnnotator(),
|
|
101
86
|
onAnnotateToggle: () => this.#syncAnnotator(),
|
|
102
87
|
onCollapse: () => this.#syncAnnotator(),
|
|
88
|
+
onSyncNow: () => this.#onControl?.({ kind: HumanControlKind.SYNC }),
|
|
103
89
|
settings: {
|
|
104
90
|
onBeforeOpen: () => {
|
|
105
91
|
if (this.#shell.isCollapsed())
|
|
@@ -110,26 +96,18 @@ export class Presenter {
|
|
|
110
96
|
},
|
|
111
97
|
});
|
|
112
98
|
}
|
|
113
|
-
/** Setter so reticle.ts can wire the control callback after construction. */
|
|
114
99
|
setControlHandler(handler) {
|
|
115
100
|
this.#onControl = handler;
|
|
116
101
|
}
|
|
117
|
-
/** Current live-control session state mirrored onto the panel (data-reticle-state). */
|
|
118
102
|
get state() {
|
|
119
103
|
return this.#panel.state;
|
|
120
104
|
}
|
|
121
|
-
/** Whether a run is currently being presented (false before the agent's first activity / after end). */
|
|
122
105
|
get sessionActive() {
|
|
123
106
|
return this.#sessionActive;
|
|
124
107
|
}
|
|
125
|
-
/** Drive the panel's live-control visual state (server-push / agent path; never emits). */
|
|
126
108
|
setState(state, text, tone) {
|
|
127
109
|
this.#panel.setState(state, text, tone);
|
|
128
110
|
}
|
|
129
|
-
/** Apply a bridge→browser presenter push: PRESENTER (state echo) or FLOWS (replay list, the human's
|
|
130
|
-
* no-agent replay surface). Owns the wire parsing so the SDK dispatcher stays a thin router;
|
|
131
|
-
* setState-only so an echo can't re-emit. */
|
|
132
|
-
/** Re-scope the replay-flow chips to the current page (called by the SDK on route change). */
|
|
133
111
|
refilterFlows() {
|
|
134
112
|
this.#panel.refilterFlows();
|
|
135
113
|
}
|
|
@@ -139,8 +117,15 @@ export class Presenter {
|
|
|
139
117
|
return void this.#panel.setFlows(a['flows']);
|
|
140
118
|
if (command.name === ReticleCommand.IMPACT) {
|
|
141
119
|
const snapshot = parseImpactSnapshot(a['snapshot']);
|
|
142
|
-
if (snapshot !== undefined)
|
|
120
|
+
if (snapshot !== undefined) {
|
|
143
121
|
this.#shell.report.setSnapshot(snapshot);
|
|
122
|
+
this.#shell.paintAccount(snapshot.account, snapshot.dashboardUrl, {
|
|
123
|
+
projectName: snapshot.projectName,
|
|
124
|
+
dashboardUrl: snapshot.dashboardUrl,
|
|
125
|
+
verdicts: snapshot.project.counts.verdicts,
|
|
126
|
+
defects: snapshot.project.counts.failed,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
144
129
|
return;
|
|
145
130
|
}
|
|
146
131
|
const state = a['state'];
|
|
@@ -149,7 +134,6 @@ export class Presenter {
|
|
|
149
134
|
if (isSessionState(state))
|
|
150
135
|
this.setState(state, text, isPresenterTone(tone) ? tone : undefined);
|
|
151
136
|
}
|
|
152
|
-
/** Current cap on accumulated log rows. */
|
|
153
137
|
get logMax() {
|
|
154
138
|
return this.#logMax;
|
|
155
139
|
}
|
|
@@ -166,10 +150,6 @@ export class Presenter {
|
|
|
166
150
|
document.head.appendChild(style);
|
|
167
151
|
const root = document.createElement('div');
|
|
168
152
|
root.setAttribute('data-reticle-overlay', '');
|
|
169
|
-
// The mode rides ON the status row, next to the dot and the elapsed time it already reports.
|
|
170
|
-
// It used to be a sibling BELOW the strip, hidden except while reading or acting — so every
|
|
171
|
-
// single tool call popped a block into the panel and took it away again, which reads as a
|
|
172
|
-
// second UI flashing in rather than as the one status line changing what it says.
|
|
173
153
|
const actStrip = `<div class="reticle-act-strip" data-liveness="idle"><span class="reticle-act-dot" aria-hidden="true"></span><span class="reticle-act">${ACT_STRIP.READY}</span><span class="reticle-chip" data-reticle-chip></span></div>`;
|
|
174
154
|
root.innerHTML = `
|
|
175
155
|
${blockerHtml()}
|
|
@@ -195,12 +175,10 @@ export class Presenter {
|
|
|
195
175
|
this.#shell.mount(root);
|
|
196
176
|
syncPageBlocker(root, getPresenterSettings(), false);
|
|
197
177
|
this.#glowCtl.setElements(this.#glow, this.#cursor);
|
|
198
|
-
// The panel queries its refs, binds listeners, and paints the initial active state.
|
|
199
178
|
this.#panel.mount(root, this.#glow);
|
|
200
179
|
this.setMode(this.#mode);
|
|
201
180
|
this.#renderTally();
|
|
202
181
|
}
|
|
203
|
-
/** Wire annotation chrome; expanding the HUD enters annotate mode. */
|
|
204
182
|
bindAnnotator(annotator) {
|
|
205
183
|
this.#annotator = annotator;
|
|
206
184
|
const root = this.#root;
|
|
@@ -220,12 +198,7 @@ export class Presenter {
|
|
|
220
198
|
annotator.setAccent(statusTheme(getPresenterSettings().statusThemeId).active);
|
|
221
199
|
this.#syncAnnotator();
|
|
222
200
|
}
|
|
223
|
-
/** Annotate whenever the HUD is open and the person asked for it - agent or no agent. */
|
|
224
201
|
#syncAnnotator() {
|
|
225
|
-
// Two things have to agree, and both are the user's: the HUD is open, and annotate is switched
|
|
226
|
-
// on. Session state used to be a third, so the mode was refused the moment the agent
|
|
227
|
-
// disconnected - which is precisely when someone opens the HUD to record what they just saw.
|
|
228
|
-
// The button still lit up, so the refusal was invisible: no outline, no composer, no reason.
|
|
229
202
|
const live = !this.#shell.isCollapsed() && this.#shell.isAnnotateOn();
|
|
230
203
|
this.#annotator?.toggle(live);
|
|
231
204
|
if (this.#root !== undefined) {
|
|
@@ -246,19 +219,13 @@ export class Presenter {
|
|
|
246
219
|
document.querySelectorAll('style[data-reticle-overlay]').forEach((s) => s.remove());
|
|
247
220
|
this.#root = undefined;
|
|
248
221
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Session start: in 'session' border mode this fades the base border IN and keeps it on until
|
|
251
|
-
* sessionEnd. Idempotent, and a no-op when unmounted or in 'busy' border mode.
|
|
252
|
-
*/
|
|
253
222
|
sessionStart() {
|
|
254
|
-
// Returning agent activity after the session ended (idle or explicit) revives it as a fresh run.
|
|
255
223
|
if (this.state === SessionState.ENDED) {
|
|
256
224
|
this.#revive();
|
|
257
225
|
return;
|
|
258
226
|
}
|
|
259
227
|
if (this.#sessionActive)
|
|
260
228
|
return;
|
|
261
|
-
// A bridge that answered supersedes whatever the failed connect left on screen.
|
|
262
229
|
if (UNREACHABLE_STATE === this.#root?.getAttribute(STATE_ATTR))
|
|
263
230
|
this.#root.removeAttribute(STATE_ATTR);
|
|
264
231
|
this.#sessionActive = true;
|
|
@@ -271,43 +238,25 @@ export class Presenter {
|
|
|
271
238
|
this.#shell.openChat();
|
|
272
239
|
}
|
|
273
240
|
}
|
|
274
|
-
/**
|
|
275
|
-
* The bridge never answered: show that, rather than showing nothing.
|
|
276
|
-
*
|
|
277
|
-
* An instrumented page with a dead bridge used to be indistinguishable from a page with no
|
|
278
|
-
* Reticle in it — overlay mounted, dock off, nothing on screen. The user cannot tell "I forgot to
|
|
279
|
-
* start the daemon" from "the install did not work", and the commonest cause is the cheapest to
|
|
280
|
-
* say: the port. So the HUD appears, states the URL it tried, and marks itself unreachable so it
|
|
281
|
-
* is never mistaken for a live session.
|
|
282
|
-
*
|
|
283
|
-
* Not an error dialog and not modal: the page is the user's, and a dev overlay that shouts is one
|
|
284
|
-
* they turn off. It is the same capsule they would have had, saying the one thing it knows.
|
|
285
|
-
*/
|
|
286
241
|
showUnreachable(url, attempts) {
|
|
287
242
|
if (this.#sessionActive)
|
|
288
243
|
return;
|
|
289
244
|
this.#root?.setAttribute(STATE_ATTR, UNREACHABLE_STATE);
|
|
290
|
-
// Dock and HUD, but NOT the border glow: the glow means the agent is working, and nothing is.
|
|
291
245
|
const dock = this.#root?.querySelector('[data-reticle-dock]');
|
|
292
246
|
dock?.setAttribute(DATA_ON, GLOW_ON);
|
|
293
247
|
this.#hud?.setAttribute(DATA_ON, GLOW_ON);
|
|
294
248
|
this.#lastActionText = unreachableStripText(url, attempts);
|
|
295
249
|
this.#paintActStrip(this.#lastActionText, true);
|
|
296
|
-
// The message IS the reason this state exists, and a collapsed capsule hides it. Same setting
|
|
297
|
-
// a live session honours, so a user who wants the bare toolbar still gets one.
|
|
298
250
|
if (getPresenterSettings().autoOpenChat)
|
|
299
251
|
this.#shell.openChat();
|
|
300
252
|
}
|
|
301
|
-
/** Turn the base border (session mode) + the HUD/log on - the visible "session is live" state. */
|
|
302
253
|
#showSession() {
|
|
303
254
|
const dock = this.#root?.querySelector('[data-reticle-dock]');
|
|
304
255
|
dock?.setAttribute(DATA_ON, GLOW_ON);
|
|
305
256
|
this.#hud?.setAttribute(DATA_ON, GLOW_ON);
|
|
306
|
-
// Base border persists in 'session' mode; 'busy' mode leaves it to the busy machine.
|
|
307
257
|
if (this.#borderMode === BorderMode.SESSION)
|
|
308
258
|
this.#glow?.setAttribute(DATA_ON, GLOW_ON);
|
|
309
259
|
}
|
|
310
|
-
/** Revive after an ended session (new agent activity): clear the ended state + glow back on. */
|
|
311
260
|
#revive() {
|
|
312
261
|
this.#panel.setState(SessionState.ACTIVE);
|
|
313
262
|
this.#endMs = undefined;
|
|
@@ -315,10 +264,6 @@ export class Presenter {
|
|
|
315
264
|
this.#glowCtl.resetActivity(this.#now());
|
|
316
265
|
this.#startHeartbeat();
|
|
317
266
|
}
|
|
318
|
-
/**
|
|
319
|
-
* Session end: hides the log/HUD and (in 'session' mode) clears the base border. Idempotent; a
|
|
320
|
-
* no-op without a prior sessionStart or when unmounted.
|
|
321
|
-
*/
|
|
322
267
|
sessionEnd() {
|
|
323
268
|
if (!this.#sessionActive)
|
|
324
269
|
return;
|
|
@@ -336,30 +281,18 @@ export class Presenter {
|
|
|
336
281
|
this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
|
|
337
282
|
}
|
|
338
283
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Record agent activity. Idempotent while busy - only the first activity from idle/fading flips
|
|
341
|
-
* the glow on, so a burst never restarts the reticle-pulse animation (no strobe). Subsequent calls
|
|
342
|
-
* just refresh the last-activity timestamp and re-arm the idle check.
|
|
343
|
-
*/
|
|
344
284
|
markActivity() {
|
|
345
285
|
this.#glowCtl.markActivity();
|
|
346
286
|
}
|
|
347
|
-
/** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
|
|
348
287
|
scheduleIdle() {
|
|
349
288
|
this.#glowCtl.scheduleIdle();
|
|
350
289
|
}
|
|
351
|
-
/** Test/diagnostic accessor for the current glow phase. */
|
|
352
290
|
glowPhase() {
|
|
353
291
|
return this.#glowCtl.phase();
|
|
354
292
|
}
|
|
355
|
-
/** Current intent (reading vs acting), exposed for tests + the watcher. */
|
|
356
293
|
get mode() {
|
|
357
294
|
return this.#mode;
|
|
358
295
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Set the presenter intent. READING shows a cyan scan + chip and hides the cursor; ACTING
|
|
361
|
-
* keeps the warm cursor/ripple + chip; IDLE clears the chip. Drives color via data-reticle-mode.
|
|
362
|
-
*/
|
|
363
296
|
setMode(mode) {
|
|
364
297
|
this.#mode = mode;
|
|
365
298
|
this.#root?.setAttribute('data-reticle-mode', mode);
|
|
@@ -381,7 +314,6 @@ export class Presenter {
|
|
|
381
314
|
this.#chip.appendChild(labelEl);
|
|
382
315
|
}
|
|
383
316
|
}
|
|
384
|
-
// READING has no real pointer to show (synthetic-hover pointer is native-only) - hide the cursor.
|
|
385
317
|
if (mode === PresenterMode.READING)
|
|
386
318
|
this.#cursor?.setAttribute(DATA_ON, GLOW_OFF);
|
|
387
319
|
}
|
|
@@ -392,13 +324,6 @@ export class Presenter {
|
|
|
392
324
|
this.#lastActionText = text;
|
|
393
325
|
this.#paintActStrip(text, false);
|
|
394
326
|
}
|
|
395
|
-
/**
|
|
396
|
-
* Sync act-strip text + the live/idle state.
|
|
397
|
-
*
|
|
398
|
-
* The liveness is mirrored onto the overlay root as well as the strip, because that is what the
|
|
399
|
-
* status COLOUR resolves against: the page glow, the collapsed FAB's halo and the minimised
|
|
400
|
-
* capsule all live outside the strip and still have to say whether the agent is working.
|
|
401
|
-
*/
|
|
402
327
|
#paintActStrip(text, idle) {
|
|
403
328
|
if (this.#actLine !== undefined)
|
|
404
329
|
this.#actLine.textContent = text;
|
|
@@ -407,9 +332,6 @@ export class Presenter {
|
|
|
407
332
|
this.#actStrip.setAttribute('data-liveness', liveness);
|
|
408
333
|
this.#root?.setAttribute(LIVENESS_ATTR, liveness);
|
|
409
334
|
if (this.#chatPillText !== undefined) {
|
|
410
|
-
// The capsule reads "logo | what the agent did | how long ago | expand", so it takes the
|
|
411
|
-
// ACTION, never the composed "idle · 12s since last action" line the strip shows: the age
|
|
412
|
-
// lives in its own slot next to it.
|
|
413
335
|
this.#chatPillText.textContent = idle
|
|
414
336
|
? this.#lastActionText !== ''
|
|
415
337
|
? this.#lastActionText
|
|
@@ -417,12 +339,6 @@ export class Presenter {
|
|
|
417
339
|
: text;
|
|
418
340
|
}
|
|
419
341
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Liveness heartbeat (native 1s timer - never rAF, so it ticks in a foreground tab regardless of
|
|
422
|
-
* agent activity). Once the agent has been quiet for IDLE_NOTICE_MS, the act strip shows a LIVE,
|
|
423
|
-
* growing "◌ idle · {duration} since last action" - the signal that was missing when a stopped
|
|
424
|
-
* agent left the panel frozen and indistinguishable from one still thinking.
|
|
425
|
-
*/
|
|
426
342
|
#startHeartbeat() {
|
|
427
343
|
if (this.#heartbeatTimer !== undefined)
|
|
428
344
|
nativeClearTimeout(this.#heartbeatTimer);
|
|
@@ -436,39 +352,35 @@ export class Presenter {
|
|
|
436
352
|
if (!this.#sessionActive || this.#actLine === undefined)
|
|
437
353
|
return;
|
|
438
354
|
if (this.state === SessionState.ENDED)
|
|
439
|
-
return;
|
|
355
|
+
return;
|
|
440
356
|
const idleMs = this.#now() - this.#glowCtl.lastActivityMs();
|
|
441
357
|
if (idleMs >= this.#idleEndMs) {
|
|
442
|
-
this.#endIdle(idleMs);
|
|
358
|
+
this.#endIdle(idleMs);
|
|
443
359
|
return;
|
|
444
360
|
}
|
|
445
361
|
if (idleMs < this.#idleNoticeMs)
|
|
446
|
-
return;
|
|
447
|
-
|
|
448
|
-
|
|
362
|
+
return;
|
|
363
|
+
if ('' === this.#lastActionText)
|
|
364
|
+
return;
|
|
365
|
+
const since = ACT_STRIP.SINCE_LAST;
|
|
366
|
+
this.#paintActStrip(`${ACT_STRIP.PLANNING_PREFIX}${humanDuration(idleMs)}${since}`, true);
|
|
449
367
|
if (this.#chatPillTime !== undefined)
|
|
450
368
|
this.#chatPillTime.textContent = humanDuration(idleMs);
|
|
451
369
|
}
|
|
452
|
-
/** Auto-end after the idle window: stamp the end, drive the panel to ENDED, stop the heartbeat. */
|
|
453
370
|
#endIdle(idleMs) {
|
|
454
371
|
this.#endMs = this.#now();
|
|
455
|
-
this.#panel.setState(SessionState.ENDED,
|
|
372
|
+
this.#panel.setState(SessionState.ENDED, `${ACT_STRIP.STOPPED_PREFIX}${humanDuration(idleMs)} quiet`);
|
|
373
|
+
this.#paintActStrip(ACT_STRIP.STOPPED, false);
|
|
456
374
|
if (this.#heartbeatTimer !== undefined) {
|
|
457
375
|
nativeClearTimeout(this.#heartbeatTimer);
|
|
458
376
|
this.#heartbeatTimer = undefined;
|
|
459
377
|
}
|
|
460
378
|
}
|
|
461
|
-
/** Agent-tunable idle-end window (reticle_session). Floored so it can't be set uselessly small. */
|
|
462
379
|
setIdleEndMs(ms) {
|
|
463
380
|
if (!Number.isFinite(ms))
|
|
464
381
|
return;
|
|
465
382
|
this.#idleEndMs = Math.max(IDLE_END_MIN_MS, Math.floor(ms));
|
|
466
383
|
}
|
|
467
|
-
/**
|
|
468
|
-
* The exported "run state" for the Copy/Export buttons - everything the page holds about this
|
|
469
|
-
* run: session id, url, duration, capability surface, per-kind counts, and the full activity log.
|
|
470
|
-
* (The full network/console ring-buffer lives server-side; this is the in-page run summary.)
|
|
471
|
-
*/
|
|
472
384
|
runState() {
|
|
473
385
|
const base = buildRunState({
|
|
474
386
|
sessionId: this.#sessionId,
|
|
@@ -518,11 +430,6 @@ export class Presenter {
|
|
|
518
430
|
syncPageBlocker(this.#root, settings, live);
|
|
519
431
|
}
|
|
520
432
|
}
|
|
521
|
-
/**
|
|
522
|
-
* Append an activity-log row. Accumulates (never overwrites): each call adds a timestamped row
|
|
523
|
-
* with a mode chip + text. Returns a handle to stamp the row's outcome glyph (✓/✗) later, or
|
|
524
|
-
* undefined when unmounted / when the text is empty after trimming.
|
|
525
|
-
*/
|
|
526
433
|
log(kind, text, result) {
|
|
527
434
|
const ms = this.#now();
|
|
528
435
|
this.#glowCtl.markActivity(ms);
|
|
@@ -532,7 +439,6 @@ export class Presenter {
|
|
|
532
439
|
if (0 === trimmed.length)
|
|
533
440
|
return undefined;
|
|
534
441
|
this.#logBaseMs ??= ms;
|
|
535
|
-
// Structured run-log entry (mirrors the DOM row) for the exported run state, capped like the DOM.
|
|
536
442
|
const entry = result !== undefined
|
|
537
443
|
? { at: ms - this.#logBaseMs, kind, text: trimmed, result }
|
|
538
444
|
: { at: ms - this.#logBaseMs, kind, text: trimmed };
|
|
@@ -546,16 +452,14 @@ export class Presenter {
|
|
|
546
452
|
if (this.#shell.isCollapsed())
|
|
547
453
|
this.#shell.pulseFab(true);
|
|
548
454
|
this.#renderTally();
|
|
549
|
-
// Wrap the handle so a later outcome stamp updates BOTH the DOM glyph and the run-log entry.
|
|
550
455
|
return {
|
|
551
456
|
result: (r) => {
|
|
552
457
|
handle.result(r);
|
|
553
458
|
entry.result = r;
|
|
554
|
-
this.#renderTally();
|
|
459
|
+
this.#renderTally();
|
|
555
460
|
},
|
|
556
461
|
};
|
|
557
462
|
}
|
|
558
|
-
/** Repaint the header verdict tally from the run log; the side that grew gets a one-shot pop. */
|
|
559
463
|
#renderTally() {
|
|
560
464
|
if (!getPresenterSettings().showTally) {
|
|
561
465
|
this.#tally?.setAttribute('hidden', '');
|
|
@@ -563,7 +467,6 @@ export class Presenter {
|
|
|
563
467
|
}
|
|
564
468
|
this.#tallied = renderTally(this.#tally, this.#runLog, this.#tallied);
|
|
565
469
|
}
|
|
566
|
-
/** Back-compat: narration appends to the live log (append-only, never overwrites). */
|
|
567
470
|
narrate(text, level = 'info') {
|
|
568
471
|
const line = 'info' === level ? text : `[${level}] ${text}`;
|
|
569
472
|
return this.log(LOG_KIND.NARRATION, line);
|
|
@@ -575,11 +478,6 @@ export class Presenter {
|
|
|
575
478
|
this.#log.firstElementChild?.remove();
|
|
576
479
|
}
|
|
577
480
|
}
|
|
578
|
-
/**
|
|
579
|
-
* Mirror the server's session.throttled state onto the HUD border. When throttled (tab
|
|
580
|
-
* backgrounded or stale), the border turns amber so the developer knows actions are no-oping -
|
|
581
|
-
* the same signal the agent already reads from result.session.throttled.
|
|
582
|
-
*/
|
|
583
481
|
setThrottled(throttled) {
|
|
584
482
|
this.#root?.setAttribute(THROTTLED_ATTR, throttled ? '1' : '0');
|
|
585
483
|
if (throttled && this.#actLine !== undefined) {
|
|
@@ -587,7 +485,6 @@ export class Presenter {
|
|
|
587
485
|
'Tab backgrounded - actions throttled. Bring tab to front or use `reticle drive`.';
|
|
588
486
|
}
|
|
589
487
|
}
|
|
590
|
-
/** Fly the cursor to an element, play the action's effect, then pace for the human. */
|
|
591
488
|
async beforeAct(refId, action, label) {
|
|
592
489
|
const el = refs.resolve(refId);
|
|
593
490
|
this.status(`${actionVerb(action)} ${label}`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TourAnchor, type TourPrompt } from '@reticlehq/core/tour';
|
|
2
|
+
export declare const HANDOFF_INDEX: number;
|
|
3
|
+
export declare const SLIDE_COUNT: number;
|
|
4
|
+
export declare const TOUR_ATTR = "data-reticle-tour";
|
|
5
|
+
export declare const TOUR_TARGET_ATTR = "data-reticle-tour-target";
|
|
6
|
+
export declare const TOUR_PROMPT_ATTR = "data-reticle-tour-prompt";
|
|
7
|
+
export declare const COPY_LABEL = "Copy";
|
|
8
|
+
export declare const COPIED_LABEL = "Copied";
|
|
9
|
+
export declare const COPY_MANUAL_LABEL = "Selected \u2014 press Ctrl/Cmd+C";
|
|
10
|
+
export declare const COPY_FLASH_MS = 1600;
|
|
11
|
+
export interface TourSlide {
|
|
12
|
+
readonly index: number;
|
|
13
|
+
readonly title: string;
|
|
14
|
+
readonly body: string;
|
|
15
|
+
readonly anchor: TourAnchor;
|
|
16
|
+
readonly tryIt?: string;
|
|
17
|
+
readonly prompts?: readonly TourPrompt[];
|
|
18
|
+
}
|
|
19
|
+
export declare const isInteractive: (slide: TourSlide) => boolean;
|
|
20
|
+
export declare function tourSlides(): readonly TourSlide[];
|
|
21
|
+
export declare function escapeHtml(text: string): string;
|
|
22
|
+
export declare function withInlineCode(text: string): string;
|
|
23
|
+
export declare function nextIndex(current: number, delta: number): number;
|
|
24
|
+
export declare const isLastSlide: (index: number) => boolean;
|
|
25
|
+
export interface TourRect {
|
|
26
|
+
readonly left: number;
|
|
27
|
+
readonly top: number;
|
|
28
|
+
readonly width: number;
|
|
29
|
+
readonly height: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function holeRects(box: TourRect, viewWidth: number, viewHeight: number): readonly TourRect[];
|
|
32
|
+
export declare function slideHtml(slide: TourSlide): string;
|
|
33
|
+
export declare const TOUR_CSS: string;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { TOUR_STEPS, TOUR_HUD_STEPS, TOUR_HANDOFF_PROMPTS, TourAnchor, } from '@reticlehq/core/tour';
|
|
2
|
+
import { Z_TOUR } from '../chrome/layers.js';
|
|
3
|
+
const ORDERED_STEPS = [...TOUR_STEPS.slice(0, 1), ...TOUR_HUD_STEPS];
|
|
4
|
+
export const HANDOFF_INDEX = ORDERED_STEPS.length;
|
|
5
|
+
export const SLIDE_COUNT = ORDERED_STEPS.length + 1;
|
|
6
|
+
export const TOUR_ATTR = 'data-reticle-tour';
|
|
7
|
+
export const TOUR_TARGET_ATTR = 'data-reticle-tour-target';
|
|
8
|
+
export const TOUR_PROMPT_ATTR = 'data-reticle-tour-prompt';
|
|
9
|
+
export const COPY_LABEL = 'Copy';
|
|
10
|
+
export const COPIED_LABEL = 'Copied';
|
|
11
|
+
export const COPY_MANUAL_LABEL = 'Selected — press Ctrl/Cmd+C';
|
|
12
|
+
export const COPY_FLASH_MS = 1600;
|
|
13
|
+
const stepSlide = (step, index) => ({
|
|
14
|
+
index,
|
|
15
|
+
title: step.title,
|
|
16
|
+
body: step.say,
|
|
17
|
+
anchor: step.anchor ?? TourAnchor.NONE,
|
|
18
|
+
...(undefined === step.tryIt ? {} : { tryIt: step.tryIt }),
|
|
19
|
+
});
|
|
20
|
+
export const isInteractive = (slide) => undefined !== slide.tryIt;
|
|
21
|
+
export function tourSlides() {
|
|
22
|
+
return [
|
|
23
|
+
...ORDERED_STEPS.map(stepSlide),
|
|
24
|
+
{
|
|
25
|
+
index: HANDOFF_INDEX,
|
|
26
|
+
title: 'Get Started',
|
|
27
|
+
body: 'Paste one of these into Claude Code, Cursor, Codex, or whatever you use.',
|
|
28
|
+
anchor: TourAnchor.NONE,
|
|
29
|
+
prompts: TOUR_HANDOFF_PROMPTS,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function escapeHtml(text) {
|
|
34
|
+
return text
|
|
35
|
+
.split('&')
|
|
36
|
+
.join('&')
|
|
37
|
+
.split('<')
|
|
38
|
+
.join('<')
|
|
39
|
+
.split('>')
|
|
40
|
+
.join('>')
|
|
41
|
+
.split('"')
|
|
42
|
+
.join('"');
|
|
43
|
+
}
|
|
44
|
+
export function withInlineCode(text) {
|
|
45
|
+
return escapeHtml(text).replace(/`([^`]+)`/g, '<code class="reticle-tour-code">$1</code>');
|
|
46
|
+
}
|
|
47
|
+
export function nextIndex(current, delta) {
|
|
48
|
+
const wanted = current + delta;
|
|
49
|
+
if (wanted < 0)
|
|
50
|
+
return 0;
|
|
51
|
+
if (wanted > SLIDE_COUNT - 1)
|
|
52
|
+
return SLIDE_COUNT - 1;
|
|
53
|
+
return wanted;
|
|
54
|
+
}
|
|
55
|
+
export const isLastSlide = (index) => index === SLIDE_COUNT - 1;
|
|
56
|
+
export function holeRects(box, viewWidth, viewHeight) {
|
|
57
|
+
const left = Math.max(0, box.left);
|
|
58
|
+
const top = Math.max(0, box.top);
|
|
59
|
+
const right = Math.min(viewWidth, box.left + box.width);
|
|
60
|
+
const bottom = Math.min(viewHeight, box.top + box.height);
|
|
61
|
+
const candidates = [
|
|
62
|
+
{ left: 0, top: 0, width: viewWidth, height: top },
|
|
63
|
+
{ left: 0, top: bottom, width: viewWidth, height: viewHeight - bottom },
|
|
64
|
+
{ left: 0, top, width: left, height: bottom - top },
|
|
65
|
+
{ left: right, top, width: viewWidth - right, height: bottom - top },
|
|
66
|
+
];
|
|
67
|
+
return candidates.filter((r) => r.width > 0 && r.height > 0);
|
|
68
|
+
}
|
|
69
|
+
export function slideHtml(slide) {
|
|
70
|
+
const dots = tourSlides()
|
|
71
|
+
.map((s) => `<span class="reticle-tour-dot${s.index === slide.index ? ' is-on' : ''}" aria-hidden="true"></span>`)
|
|
72
|
+
.join('');
|
|
73
|
+
const tryIt = undefined === slide.tryIt
|
|
74
|
+
? ''
|
|
75
|
+
: `<p class="reticle-tour-try" ${TOUR_TARGET_ATTR}="try">${escapeHtml(slide.tryIt)}</p>`;
|
|
76
|
+
const prompt = undefined === slide.prompts
|
|
77
|
+
? ''
|
|
78
|
+
: `<div class="reticle-tour-prompts">` +
|
|
79
|
+
slide.prompts
|
|
80
|
+
.map((p, i) => `<div class="reticle-tour-prompt" ${TOUR_PROMPT_ATTR}="${String(i)}">` +
|
|
81
|
+
`<span class="reticle-tour-prompt-label">${escapeHtml(p.label)}</span>` +
|
|
82
|
+
`<pre class="reticle-tour-prompt-text">${escapeHtml(p.text)}</pre>` +
|
|
83
|
+
`<button type="button" class="reticle-tour-copy" ${TOUR_TARGET_ATTR}="copy" ` +
|
|
84
|
+
`${TOUR_PROMPT_ATTR}="${String(i)}">${COPY_LABEL}</button></div>`)
|
|
85
|
+
.join('') +
|
|
86
|
+
`</div>`;
|
|
87
|
+
const back = 0 === slide.index
|
|
88
|
+
? ''
|
|
89
|
+
: `<button type="button" class="reticle-tour-back" ${TOUR_TARGET_ATTR}="back">Back</button>`;
|
|
90
|
+
const forward = isLastSlide(slide.index)
|
|
91
|
+
? `<button type="button" class="reticle-tour-next" ${TOUR_TARGET_ATTR}="done">Done</button>`
|
|
92
|
+
: `<button type="button" class="reticle-tour-next" ${TOUR_TARGET_ATTR}="next">Next</button>`;
|
|
93
|
+
return (`<div class="reticle-tour-card" role="dialog" aria-modal="false" aria-label="Reticle tour">` +
|
|
94
|
+
`<button type="button" class="reticle-tour-skip" ${TOUR_TARGET_ATTR}="skip" aria-label="Skip the tour">Skip</button>` +
|
|
95
|
+
`<p class="reticle-tour-step">Step ${String(slide.index + 1)} of ${String(SLIDE_COUNT)}</p>` +
|
|
96
|
+
`<h2 class="reticle-tour-title">${escapeHtml(slide.title)}</h2>` +
|
|
97
|
+
`<p class="reticle-tour-body">${withInlineCode(slide.body)}</p>` +
|
|
98
|
+
tryIt +
|
|
99
|
+
prompt +
|
|
100
|
+
`<div class="reticle-tour-foot"><div class="reticle-tour-dots">${dots}</div>` +
|
|
101
|
+
`<div class="reticle-tour-nav">${back}${forward}</div></div>` +
|
|
102
|
+
`</div>`);
|
|
103
|
+
}
|
|
104
|
+
export const TOUR_CSS = `
|
|
105
|
+
[${TOUR_ATTR}]{position:fixed;inset:0;z-index:${String(Z_TOUR)};pointer-events:none;
|
|
106
|
+
font:13px/1.55 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
|
|
107
|
+
[${TOUR_ATTR}] .reticle-tour-scrim{position:absolute;inset:0;background:rgba(6,8,14,.55);
|
|
108
|
+
backdrop-filter:blur(1.5px);pointer-events:auto;z-index:0;}
|
|
109
|
+
/* Blocks the click everywhere the hole is not. Same job as the scrim, minus the one rectangle the
|
|
110
|
+
slide is inviting somebody to press. */
|
|
111
|
+
[${TOUR_ATTR}] .reticle-tour-blocker{position:absolute;pointer-events:auto;z-index:0;}
|
|
112
|
+
/* Ringed slides dim through the ring's own 9999px shadow, which leaves a hole over the thing being
|
|
113
|
+
pointed at. The scrim would cover that hole -- dimming the HUD the slide is ABOUT, and washing
|
|
114
|
+
the page twice (~80% rather than 55%). Cleared, not removed: it is also what swallows clicks
|
|
115
|
+
meant for the app underneath, and a tour that lets you click through is not a tour. */
|
|
116
|
+
[${TOUR_ATTR}] .reticle-tour-scrim.is-clear{background:transparent;backdrop-filter:none;}
|
|
117
|
+
/* Above the ring, and the z-index is the whole point.
|
|
118
|
+
The ring is appended after the card, and both are positioned with an auto z-index, so the ring
|
|
119
|
+
painted on top of it -- and the ring's dimming IS a 9999px shadow, which therefore fell across the
|
|
120
|
+
card as well. On every ringed slide the card was washed at 55% and read as sitting BEHIND the page
|
|
121
|
+
it was drawn over. The ring must stay above the scrim and below the card; saying so explicitly is
|
|
122
|
+
the fix, because DOM order alone cannot express "between these two". */
|
|
123
|
+
[${TOUR_ATTR}] .reticle-tour-card{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
|
|
124
|
+
width:min(430px,calc(100vw - 32px));box-sizing:border-box;pointer-events:auto;z-index:3;
|
|
125
|
+
background:#14161d;color:#e8eaf0;border:1px solid #2b303d;border-radius:14px;padding:20px 20px 16px;
|
|
126
|
+
box-shadow:0 24px 60px rgba(0,0,0,.5);}
|
|
127
|
+
[${TOUR_ATTR}] .reticle-tour-skip{position:absolute;top:10px;right:12px;background:none;border:0;
|
|
128
|
+
color:#8a93a6;font:inherit;font-size:12px;cursor:pointer;padding:4px 6px;border-radius:6px;}
|
|
129
|
+
[${TOUR_ATTR}] .reticle-tour-skip:hover{color:#e8eaf0;background:rgba(255,255,255,.06);}
|
|
130
|
+
[${TOUR_ATTR}] .reticle-tour-step{margin:0 0 6px;font-size:11px;letter-spacing:.06em;
|
|
131
|
+
text-transform:uppercase;color:#7f8798;}
|
|
132
|
+
[${TOUR_ATTR}] .reticle-tour-title{margin:0 0 8px;font-size:18px;line-height:1.25;font-weight:650;
|
|
133
|
+
color:#f2f4f8;}
|
|
134
|
+
[${TOUR_ATTR}] .reticle-tour-body{margin:0 0 10px;color:#c9cedb;}
|
|
135
|
+
/* A call named inside a sentence. Sets its own colour for the reason every other text class does:
|
|
136
|
+
an inherited one loses to any direct element selector in the host app. */
|
|
137
|
+
[${TOUR_ATTR}] .reticle-tour-code{padding:1px 5px;border-radius:5px;background:#0c0e14;
|
|
138
|
+
border:1px solid #232834;color:#9fb6ff;
|
|
139
|
+
font:11.5px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;overflow-wrap:anywhere;}
|
|
140
|
+
[${TOUR_ATTR}] .reticle-tour-try{margin:0 0 12px;padding:8px 10px;border-radius:8px;
|
|
141
|
+
background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.32);color:#c7cbff;
|
|
142
|
+
font-size:12.5px;font-weight:550;}
|
|
143
|
+
[${TOUR_ATTR}] .reticle-tour-prompts{display:flex;flex-direction:column;gap:8px;margin:0 0 12px;}
|
|
144
|
+
[${TOUR_ATTR}] .reticle-tour-prompt{display:grid;gap:6px;padding:9px 10px;border-radius:8px;
|
|
145
|
+
background:#0c0e14;border:1px solid #232834;
|
|
146
|
+
grid-template-columns:1fr auto;grid-template-areas:"label copy" "text text";align-items:center;}
|
|
147
|
+
[${TOUR_ATTR}] .reticle-tour-prompt-label{grid-area:label;color:#8a93a6;font-size:11px;
|
|
148
|
+
letter-spacing:.05em;text-transform:uppercase;}
|
|
149
|
+
[${TOUR_ATTR}] .reticle-tour-prompt-text{grid-area:text;width:100%;box-sizing:border-box;margin:0;
|
|
150
|
+
white-space:pre-wrap;overflow-wrap:anywhere;color:#c9cedb;
|
|
151
|
+
font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;}
|
|
152
|
+
[${TOUR_ATTR}] .reticle-tour-copy{grid-area:copy;padding:5px 10px;border-radius:6px;
|
|
153
|
+
border:1px solid #2b303d;background:transparent;color:#c9cedb;font:inherit;font-size:11.5px;
|
|
154
|
+
font-weight:600;cursor:pointer;white-space:nowrap;}
|
|
155
|
+
[${TOUR_ATTR}] .reticle-tour-copy:hover{background:rgba(255,255,255,.07);color:#fff;}
|
|
156
|
+
/* The flash that tells somebody the click did something. Colour AND text: colour alone is not an
|
|
157
|
+
answer for the people who cannot see the difference. */
|
|
158
|
+
[${TOUR_ATTR}] .reticle-tour-copy.is-done{background:#16a34a;border-color:#16a34a;color:#fff;}
|
|
159
|
+
[${TOUR_ATTR}] .reticle-tour-copy.is-manual{background:#b45309;border-color:#b45309;color:#fff;}
|
|
160
|
+
[${TOUR_ATTR}] .reticle-tour-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;}
|
|
161
|
+
[${TOUR_ATTR}] .reticle-tour-dots{display:flex;gap:6px;}
|
|
162
|
+
[${TOUR_ATTR}] .reticle-tour-dot{width:6px;height:6px;border-radius:50%;background:#333a49;}
|
|
163
|
+
[${TOUR_ATTR}] .reticle-tour-dot.is-on{background:#6366f1;}
|
|
164
|
+
[${TOUR_ATTR}] .reticle-tour-nav{display:flex;gap:8px;}
|
|
165
|
+
[${TOUR_ATTR}] .reticle-tour-back,[${TOUR_ATTR}] .reticle-tour-next{padding:8px 14px;border-radius:8px;
|
|
166
|
+
font:inherit;font-weight:600;cursor:pointer;border:1px solid #2b303d;background:transparent;color:#c9cedb;}
|
|
167
|
+
[${TOUR_ATTR}] .reticle-tour-next{background:#6366f1;border-color:#6366f1;color:#fff;}
|
|
168
|
+
[${TOUR_ATTR}] .reticle-tour-next:hover{filter:brightness(1.08);}
|
|
169
|
+
[${TOUR_ATTR}] .reticle-tour-back:hover{background:rgba(255,255,255,.06);color:#e8eaf0;}
|
|
170
|
+
[${TOUR_ATTR}] .reticle-tour-ring{position:absolute;border-radius:14px;pointer-events:none;z-index:1;
|
|
171
|
+
box-shadow:0 0 0 3px #6366f1,0 0 0 9999px rgba(6,8,14,.55);transition:all .25s ease;}
|
|
172
|
+
/* A ring somebody is being asked to press, rather than to look at: it pulses, so the invitation in
|
|
173
|
+
the prose has something on the page agreeing with it. */
|
|
174
|
+
[${TOUR_ATTR}] .reticle-tour-ring.is-live{animation:reticle-tour-pulse 1.8s ease-in-out infinite;}
|
|
175
|
+
@keyframes reticle-tour-pulse{
|
|
176
|
+
0%,100%{box-shadow:0 0 0 3px #6366f1,0 0 0 9999px rgba(6,8,14,.55);}
|
|
177
|
+
50%{box-shadow:0 0 0 6px rgba(99,102,241,.55),0 0 0 9999px rgba(6,8,14,.55);}
|
|
178
|
+
}
|
|
179
|
+
/* A REGION, not a target. The spotlight above cuts a hole in the dimming, which is right for
|
|
180
|
+
something small and wrong for the app: the hole becomes the whole viewport, the dimming
|
|
181
|
+
disappears, and the card is left competing with a fully lit page while the ring edges sit off at
|
|
182
|
+
the margins pointing at nothing. An outline says "all of this" without turning the lights on. */
|
|
183
|
+
[${TOUR_ATTR}] .reticle-tour-ring.is-region{box-shadow:none;border:2px dashed #7c83f5;
|
|
184
|
+
background:rgba(99,102,241,.06);}
|
|
185
|
+
@media (prefers-reduced-motion:reduce){
|
|
186
|
+
[${TOUR_ATTR}] .reticle-tour-ring{transition:none;}
|
|
187
|
+
}
|
|
188
|
+
`;
|