@reticlehq/browser 3.5.0 → 3.6.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 +44 -43
- package/dist/actions/actions-press.d.ts +10 -4
- package/dist/actions/actions-press.js +108 -17
- package/dist/actions/actions.d.ts +2 -0
- package/dist/actions/actions.js +25 -16
- package/dist/actions/danger-context.js +1 -1
- package/dist/connect-options.d.ts +1 -0
- package/dist/dom/a11y.js +13 -4
- package/dist/dom/addressing/refs.js +2 -1
- package/dist/dom/addressing/testid-attr.d.ts +4 -0
- package/dist/dom/addressing/testid-attr.js +14 -0
- package/dist/dom/query.d.ts +1 -0
- package/dist/dom/query.js +38 -11
- package/dist/dom/snapshot.js +1 -0
- package/dist/observers/departure.js +51 -9
- package/dist/observers/dom.js +3 -2
- package/dist/observers/field.js +5 -3
- package/dist/observers/health.js +63 -2
- package/dist/observers/network-stream.d.ts +1 -1
- package/dist/observers/network-stream.js +3 -3
- package/dist/observers/network.js +1 -1
- package/dist/observers/storage.js +6 -1
- package/dist/presenter/carousel/carousel.d.ts +5 -2
- package/dist/presenter/carousel/carousel.js +34 -7
- package/dist/presenter/carousel/offer-card.d.ts +2 -2
- package/dist/presenter/carousel/offer-card.js +2 -1
- package/dist/presenter/carousel/panel-slides.d.ts +4 -3
- package/dist/presenter/carousel/panel-slides.js +43 -6
- package/dist/presenter/carousel/talk-card.d.ts +1 -1
- package/dist/presenter/chrome/log-kinds.d.ts +5 -0
- package/dist/presenter/chrome/log-kinds.js +1 -0
- package/dist/presenter/chrome/log-memory.d.ts +12 -0
- package/dist/presenter/chrome/log-memory.js +39 -0
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
- package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
- package/dist/presenter/chrome/presenter-log.d.ts +5 -4
- package/dist/presenter/chrome/presenter-log.js +55 -5
- package/dist/presenter/chrome/presenter-report-copy.d.ts +11 -1
- package/dist/presenter/chrome/presenter-report-copy.js +14 -1
- package/dist/presenter/hud-telemetry.js +31 -2
- package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
- package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
- package/dist/presenter/icons/presenter-icons.d.ts +2 -0
- package/dist/presenter/icons/presenter-icons.js +2 -0
- package/dist/presenter/presenter-account.d.ts +5 -4
- package/dist/presenter/presenter-account.js +23 -10
- package/dist/presenter/presenter-chat-views.d.ts +21 -0
- package/dist/presenter/presenter-chat-views.js +382 -0
- package/dist/presenter/presenter-config.d.ts +3 -2
- package/dist/presenter/presenter-config.js +4 -3
- package/dist/presenter/presenter-controls.d.ts +4 -2
- package/dist/presenter/presenter-controls.js +231 -51
- package/dist/presenter/presenter-design-tokens.d.ts +1 -0
- package/dist/presenter/presenter-design-tokens.js +34 -0
- package/dist/presenter/presenter-dock-layout.js +1 -1
- package/dist/presenter/presenter-frame.d.ts +12 -0
- package/dist/presenter/presenter-frame.js +189 -0
- package/dist/presenter/presenter-plan.d.ts +10 -0
- package/dist/presenter/presenter-plan.js +185 -0
- package/dist/presenter/presenter-report-styles.js +7 -0
- package/dist/presenter/presenter-report.d.ts +1 -1
- package/dist/presenter/presenter-report.js +53 -16
- package/dist/presenter/presenter-settings-styles.js +10 -0
- package/dist/presenter/presenter-settings.d.ts +5 -0
- package/dist/presenter/presenter-settings.js +71 -11
- package/dist/presenter/presenter-shell-styles.js +39 -42
- package/dist/presenter/presenter-shell.d.ts +8 -1
- package/dist/presenter/presenter-shell.js +124 -72
- package/dist/presenter/presenter-styles.js +9 -1
- package/dist/presenter/presenter.d.ts +4 -3
- package/dist/presenter/presenter.js +101 -13
- package/dist/presenter/tour/tour.d.ts +4 -1
- package/dist/presenter/tour/tour.js +6 -4
- package/dist/recorder/recorder.js +2 -2
- package/dist/registry/auto-testids.js +3 -4
- package/dist/registry/declared-platform.js +2 -0
- package/dist/reticle-inject.js +880 -513
- package/dist/reticle.d.ts +10 -1
- package/dist/reticle.js +73 -9
- package/dist/review/annotator-styles.d.ts +1 -1
- package/dist/review/annotator.d.ts +7 -0
- package/dist/review/annotator.js +20 -2
- package/dist/review/mark-anchor.js +2 -2
- package/dist/session-continuity.js +2 -1
- package/dist/storage-keys.d.ts +14 -0
- package/dist/storage-keys.js +17 -0
- package/package.json +3 -2
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readTestId, testIdSelector } from '../dom/addressing/testid-attr.js';
|
|
2
|
+
import { FlowProgressStatus, HumanControlKind, PresenterTone, SessionState, } from '@reticlehq/core';
|
|
2
3
|
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
|
|
3
|
-
import {
|
|
4
|
-
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, hiIconHtml
|
|
4
|
+
import { COPY_MARKS_ATTR, MARKS_ROW_ATTR } from './presenter-config.js';
|
|
5
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, hiIconHtml } from './icons/presenter-icons.js';
|
|
5
6
|
import { getPresenterSettings } from './presenter-settings.js';
|
|
6
7
|
import { mountWorkspaceSelector, workspaceRowHtml } from './presenter-workspace.js';
|
|
8
|
+
import { CHAT_VIEWS_HTML } from './presenter-chat-views.js';
|
|
9
|
+
import { ReticleStorageKey } from '../storage-keys.js';
|
|
7
10
|
const DATA_RETICLE_STATE = 'data-reticle-state';
|
|
8
11
|
const DATA_RETICLE_TONE = 'data-reticle-tone';
|
|
9
12
|
const DATA_ON = 'data-on';
|
|
@@ -19,14 +22,43 @@ const ENDED_BANNER_TEXT = 'Session ended';
|
|
|
19
22
|
const COPY_LABEL = 'Copy run';
|
|
20
23
|
const EXPORT_LABEL = 'Export';
|
|
21
24
|
const FLOWS_LABEL = 'Replay a flow';
|
|
25
|
+
const FLOWS_ALL_LABEL = 'See all';
|
|
26
|
+
const SEE_LOGS_LABEL = 'See the logs';
|
|
27
|
+
const PROGRESS_LINGER_MS = 6000;
|
|
28
|
+
const PROGRESS_GLYPH = {
|
|
29
|
+
[FlowProgressStatus.PLAYING]: '⏵',
|
|
30
|
+
[FlowProgressStatus.PASSED]: '✓',
|
|
31
|
+
[FlowProgressStatus.FAILED]: '✗',
|
|
32
|
+
};
|
|
33
|
+
function parseFlowProgress(args) {
|
|
34
|
+
const { name, done, total, status } = args;
|
|
35
|
+
if ('string' !== typeof name || 'number' !== typeof done || 'number' !== typeof total)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (!Object.values(FlowProgressStatus).includes(status))
|
|
38
|
+
return undefined;
|
|
39
|
+
return { name, done, total, status: status };
|
|
40
|
+
}
|
|
22
41
|
const COPIED_TEXT = 'Copied ✓';
|
|
23
42
|
const RUN_FILENAME = 'reticle-run.json';
|
|
24
43
|
export const ENDED_FADE_MS = 4000;
|
|
44
|
+
const RECENT_PLAYS_KEY = ReticleStorageKey.RECENT_PLAYS;
|
|
45
|
+
const FLOW_TEXT = {
|
|
46
|
+
REPLAYABLE: 'Replay without an agent',
|
|
47
|
+
ELSEWHERE: 'Starts on another page',
|
|
48
|
+
EMPTY_TITLE: 'No saved flows yet',
|
|
49
|
+
EMPTY_HINT: 'Every journey your agent verifies is saved here, ready to replay in one click.',
|
|
50
|
+
};
|
|
51
|
+
export function flowTitle(name) {
|
|
52
|
+
const words = name.replace(/[-_]+/g, ' ').trim();
|
|
53
|
+
return 0 === words.length ? name : words.charAt(0).toUpperCase() + words.slice(1);
|
|
54
|
+
}
|
|
25
55
|
export const CONTROLS_CSS = `
|
|
26
56
|
[data-reticle-chat-panel] [data-reticle-foot]{flex:none;padding:8px 10px 10px;border-top:1px solid rgba(255,255,255,.07);
|
|
27
57
|
background:linear-gradient(180deg,rgba(0,0,0,.35) 0%,rgba(0,0,0,.55) 100%);pointer-events:auto;}
|
|
58
|
+
[data-reticle-chat-panel] .reticle-foot-workspace-row{display:flex;align-items:center;min-width:0;}
|
|
28
59
|
[data-reticle-chat-panel] .reticle-hud-log-well{margin:0 0 4px;}
|
|
29
60
|
[data-reticle-chat-panel] .reticle-workspace-wrap{position:relative;align-self:flex-start;max-width:100%;}
|
|
61
|
+
[data-reticle-chat-panel] .reticle-workspace-wrap[hidden]{display:none;}
|
|
30
62
|
[data-reticle-chat-panel] .reticle-workspace{
|
|
31
63
|
display:inline-flex;align-items:center;gap:5px;max-width:100%;padding:3px 8px 3px 6px;border-radius:999px;cursor:pointer;
|
|
32
64
|
border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.04);color:rgba(255,255,255,.78);
|
|
@@ -37,6 +69,10 @@ export const CONTROLS_CSS = `
|
|
|
37
69
|
[data-reticle-chat-panel] .reticle-workspace-icon,
|
|
38
70
|
[data-reticle-chat-panel] .reticle-workspace-caret{display:inline-flex;align-items:center;opacity:.72;flex:none;}
|
|
39
71
|
[data-reticle-chat-panel] .reticle-workspace-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:min(200px,calc(100vw - 120px));}
|
|
72
|
+
@media (max-width:480px){
|
|
73
|
+
[data-reticle-chat-panel] .reticle-workspace-wrap{flex:none;}
|
|
74
|
+
[data-reticle-chat-panel] .reticle-workspace-name{max-width:88px;}
|
|
75
|
+
}
|
|
40
76
|
[data-reticle-chat-panel] .reticle-workspace[aria-expanded="true"] .reticle-workspace-caret{transform:rotate(180deg);}
|
|
41
77
|
[data-reticle-chat-panel] .reticle-workspace-caret{transition:transform .18s ease;}
|
|
42
78
|
[data-reticle-chat-panel] .reticle-workspace-menu{
|
|
@@ -65,9 +101,9 @@ export const CONTROLS_CSS = `
|
|
|
65
101
|
[data-reticle-chat-panel] .reticle-workspace-menu-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:4px 0;}
|
|
66
102
|
[data-reticle-chat-panel] .reticle-workspace-menu-k{flex:none;color:rgba(255,255,255,.42);}
|
|
67
103
|
[data-reticle-chat-panel] .reticle-workspace-menu-v{min-width:0;text-align:right;color:rgba(255,255,255,.9);font-weight:500;word-break:break-all;}
|
|
68
|
-
[data-reticle-chat-panel] .reticle-banner{display:none;flex:none;align-items:center;gap:8px;padding:
|
|
69
|
-
font-size:12px;font-weight:
|
|
70
|
-
[data-reticle-overlay][data-reticle-state="ended"] [data-reticle-chat-panel] .reticle-banner{display:
|
|
104
|
+
[data-reticle-chat-panel] .reticle-banner{display:none;flex:none;align-items:center;gap:8px;min-height:38px;box-sizing:border-box;padding:8px 14px;
|
|
105
|
+
color:var(--reticle-hud-text);font-size:12px;font-weight:600;border-bottom:1px solid var(--reticle-hud-border);background:var(--reticle-hud-inset);line-height:1.35;}
|
|
106
|
+
[data-reticle-overlay][data-reticle-state="ended"] [data-reticle-chat-panel] .reticle-banner{display:flex;}
|
|
71
107
|
[data-reticle-overlay][data-reticle-state="paused"] [data-reticle-glow][data-on="1"]{
|
|
72
108
|
box-shadow:inset 0 0 0 2px rgba(255,255,255,.2);}
|
|
73
109
|
[data-reticle-overlay][data-reticle-state="ended"] [data-reticle-glow][data-on="1"]{
|
|
@@ -87,13 +123,30 @@ export const CONTROLS_CSS = `
|
|
|
87
123
|
[data-reticle-overlay][data-reticle-tone="ask"] [data-reticle-glow][data-on="1"],
|
|
88
124
|
[data-reticle-overlay][data-reticle-tone="warn"] [data-reticle-glow][data-on="1"]{
|
|
89
125
|
box-shadow:inset 0 0 0 2px rgba(255,255,255,.22);}
|
|
90
|
-
[data-reticle-chat-panel] .reticle-flows{display:none;flex:none;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
[data-reticle-chat-panel] .reticle-flows
|
|
94
|
-
[data-reticle-chat-panel] .reticle-flows
|
|
95
|
-
[data-reticle-chat-panel] .reticle-flows
|
|
96
|
-
[data-reticle-chat-panel] .reticle-flows-
|
|
126
|
+
[data-reticle-chat-panel] .reticle-flows{display:none;flex:none;min-width:0;padding:8px 12px;border-top:1px solid var(--reticle-line2);pointer-events:auto;}
|
|
127
|
+
[data-reticle-chat-panel] .reticle-flows[data-has="1"]{display:block;}
|
|
128
|
+
[data-reticle-chat-panel] .reticle-flows-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:5px;}
|
|
129
|
+
[data-reticle-chat-panel] .reticle-flows-all{border:0;padding:0;background:none;cursor:pointer;color:var(--reticle-c-active);font:inherit;font-size:10.5px;font-weight:500;}
|
|
130
|
+
[data-reticle-chat-panel] .reticle-flows-all:hover{text-decoration:underline;}
|
|
131
|
+
[data-reticle-chat-panel] .reticle-flows-links{display:inline-flex;gap:10px;}
|
|
132
|
+
[data-reticle-chat-panel] .reticle-flows-all[hidden]{display:none;}
|
|
133
|
+
/* A replay the person started: the chip becomes a player, its bar filling step by step. */
|
|
134
|
+
[data-reticle-chat-panel] :is(.reticle-flow,.reticle-flow-row)[data-state]{position:relative;overflow:hidden;
|
|
135
|
+
border-color:color-mix(in srgb,var(--reticle-c-active) 55%,transparent);color:var(--reticle-fg);}
|
|
136
|
+
[data-reticle-chat-panel] :is(.reticle-flow,.reticle-flow-row)[data-state]::after{content:"";position:absolute;left:0;bottom:0;height:2px;
|
|
137
|
+
width:var(--reticle-flow-progress,4%);background:var(--reticle-c-active);transition:width .25s var(--reticle-hud-ease,ease);}
|
|
138
|
+
[data-reticle-chat-panel] :is(.reticle-flow,.reticle-flow-row)[data-state="playing"]::after{animation:reticle-flow-pulse 1.2s ease-in-out infinite;}
|
|
139
|
+
@keyframes reticle-flow-pulse{50%{opacity:.55}}
|
|
140
|
+
[data-reticle-chat-panel] :is(.reticle-flow,.reticle-flow-row)[data-state="passed"]::after{background:#4ade80;}
|
|
141
|
+
[data-reticle-chat-panel] :is(.reticle-flow,.reticle-flow-row)[data-state="failed"]::after{background:#f87171;}
|
|
142
|
+
[data-reticle-chat-panel] .reticle-flows-cap{display:block;color:var(--reticle-faint);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;}
|
|
143
|
+
[data-reticle-chat-panel] .reticle-flow-strip{display:flex;gap:6px;min-width:0;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;overscroll-behavior-inline:contain;pointer-events:auto;}
|
|
144
|
+
[data-reticle-chat-panel] .reticle-flow-strip::-webkit-scrollbar{display:none;}
|
|
145
|
+
[data-reticle-chat-panel] .reticle-flow-strip .reticle-flow{flex:none;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
146
|
+
[data-reticle-chat-panel] .reticle-all-flows{flex:1;min-height:0;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;align-items:stretch;gap:5px;}
|
|
147
|
+
[data-reticle-chat-panel] .reticle-all-flows .reticle-flow{width:100%;height:auto;min-height:32px;text-align:left;justify-content:flex-start;}
|
|
148
|
+
[data-reticle-chat-panel] .reticle-all-flows .reticle-flow:disabled{opacity:.45;cursor:not-allowed;}
|
|
149
|
+
[data-reticle-chat-panel] .reticle-flows-empty{padding:25px 8px;color:var(--reticle-faint);font-size:11px;text-align:center;}
|
|
97
150
|
[data-reticle-chat-panel] .reticle-flow{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;
|
|
98
151
|
border-radius:7px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);color:var(--reticle-muted);
|
|
99
152
|
font-family:var(--reticle-font);font-size:11px;font-weight:500;transition:background .15s,color .15s,border-color .15s,transform .1s;}
|
|
@@ -122,35 +175,13 @@ function paintPauseBtn(btn, paused) {
|
|
|
122
175
|
btn.classList.toggle('reticle-tb-btn--primary', paused);
|
|
123
176
|
btn.setAttribute('data-active', paused ? '1' : '0');
|
|
124
177
|
}
|
|
125
|
-
const CHAT_LABEL = 'Agent chat';
|
|
126
|
-
const MARKERS_LABEL = 'Hide markers';
|
|
127
|
-
const CLEAR_MARKS_LABEL = 'Clear all';
|
|
128
178
|
const COPY_MARKS_LABEL = 'Copy for your agent';
|
|
129
179
|
const TB = PRESENTER_ICON_SIZE.TOOLBAR;
|
|
130
|
-
|
|
131
|
-
const shortcut = kbd === undefined ? '' : `<span class="reticle-tb-kbd">${kbd}</span>`;
|
|
132
|
-
return `<div class="reticle-tb-wrap">${btn}<span class="reticle-tb-tip">${tip}${shortcut}</span></div>`;
|
|
133
|
-
}
|
|
134
|
-
function pauseToolbarHtml() {
|
|
135
|
-
const btn = `<button type="button" data-reticle-pause class="reticle-tb-btn" title="${CONTROL_LABEL.PAUSE}" aria-label="${CONTROL_LABEL.PAUSE}">${hiIconHtml(PresenterIcon.PAUSE, TB)}</button>`;
|
|
136
|
-
const badge = `<span data-reticle-badge class="reticle-pause-badge">${PAUSED_BADGE_TEXT}</span>`;
|
|
137
|
-
const tip = `<span class="reticle-tb-tip">${CONTROL_LABEL.PAUSE}</span>`;
|
|
138
|
-
return `<div class="reticle-tb-wrap reticle-tb-wrap--pause">${btn}${badge}${tip}</div>`;
|
|
139
|
-
}
|
|
140
|
-
export const CONTROLS_TOOLBAR_HTML = [
|
|
141
|
-
pauseToolbarHtml(),
|
|
142
|
-
tbWrap(`<button type="button" ${CHAT_TOGGLE_ATTR} class="reticle-tb-btn reticle-tb-btn--toggle" title="${CHAT_LABEL}" aria-label="${CHAT_LABEL}" aria-pressed="false" data-active="0">${hiToggleIconHtml(PresenterIcon.MESSAGE, TB)}</button>`, CHAT_LABEL),
|
|
143
|
-
tbWrap(`<button type="button" ${MARKERS_BTN_ATTR} class="reticle-tb-btn reticle-tb-btn--toggle" title="${MARKERS_LABEL}" aria-label="${MARKERS_LABEL}" aria-pressed="false" data-active="0" disabled>${hiToggleIconHtml(PresenterIcon.VIEW, TB)}</button>`, MARKERS_LABEL, 'H'),
|
|
144
|
-
tbWrap(`<button type="button" data-reticle-end class="reticle-tb-btn" title="${CONTROL_LABEL.END}" aria-label="${CONTROL_LABEL.END}">${hiIconHtml(PresenterIcon.STOP, TB)}</button>`, CONTROL_LABEL.END),
|
|
145
|
-
tbWrap(`<button type="button" ${CLEAR_MARKS_ATTR} class="reticle-tb-btn" title="${CLEAR_MARKS_LABEL}" aria-label="${CLEAR_MARKS_LABEL}" data-danger disabled>${hiIconHtml(PresenterIcon.TRASH, TB)}</button>`, CLEAR_MARKS_LABEL, 'X'),
|
|
146
|
-
`<button type="button" data-reticle-copy class="reticle-tb-btn reticle-tb-btn--export" title="${COPY_LABEL}" aria-label="${COPY_LABEL}" hidden>${hiIconHtml(PresenterIcon.COPY, TB)}</button>`,
|
|
147
|
-
`<button type="button" data-reticle-export class="reticle-tb-btn reticle-tb-btn--export" title="${EXPORT_LABEL}" aria-label="${EXPORT_LABEL}" hidden>${hiIconHtml(PresenterIcon.DOWNLOAD, TB)}</button>`,
|
|
148
|
-
'<span data-reticle-export-msg class="reticle-export-msg" aria-live="polite"></span>',
|
|
149
|
-
].join('');
|
|
180
|
+
export const CONTROLS_STATUS_HTML = `<button type="button" data-reticle-pause class="reticle-status-btn" title="${CONTROL_LABEL.PAUSE}" aria-label="${CONTROL_LABEL.PAUSE}">${hiIconHtml(PresenterIcon.PAUSE, TB)}</button><span data-reticle-badge class="reticle-pause-badge">${PAUSED_BADGE_TEXT}</span><button type="button" data-reticle-end class="reticle-status-btn reticle-status-stop" title="${CONTROL_LABEL.END}" aria-label="${CONTROL_LABEL.END}">${hiIconHtml(PresenterIcon.STOP, TB)}</button><button type="button" data-reticle-copy class="reticle-status-btn" title="${COPY_LABEL}" aria-label="${COPY_LABEL}" hidden>${hiIconHtml(PresenterIcon.COPY, TB)}</button><button type="button" data-reticle-export class="reticle-status-btn" title="${EXPORT_LABEL}" aria-label="${EXPORT_LABEL}" hidden>${hiIconHtml(PresenterIcon.DOWNLOAD, TB)}</button><span data-reticle-export-msg class="reticle-export-msg" aria-live="polite"></span>`;
|
|
150
181
|
export const CONTROLS_BANNER_HTML = `<div data-reticle-banner class="reticle-banner">${ENDED_BANNER_TEXT}</div>`;
|
|
151
182
|
export const CONTROLS_MARKS_HTML = `<div ${MARKS_ROW_ATTR} class="reticle-marks-row" hidden><span data-reticle-marks-text class="reticle-marks-text"></span><button type="button" ${COPY_MARKS_ATTR} class="reticle-marks-copy" title="${COPY_MARKS_LABEL}">${hiIconHtml(PresenterIcon.COPY, PRESENTER_ICON_SIZE.HELP)}<span>${COPY_MARKS_LABEL}</span></button></div>`;
|
|
152
|
-
export const CONTROLS_FLOWS_HTML = `<div data-reticle-flows class="reticle-flows"><span class="reticle-flows-cap">${FLOWS_LABEL}</span></div>`;
|
|
153
|
-
export const CONTROLS_FOOT_HTML = `<div data-reticle-foot><div class="reticle-foot-stack">${workspaceRowHtml()}</div></div>`;
|
|
183
|
+
export const CONTROLS_FLOWS_HTML = `<div data-reticle-flows class="reticle-flows"><div class="reticle-flows-head"><span class="reticle-flows-cap">${FLOWS_LABEL}</span><span class="reticle-flows-links"><button type="button" data-reticle-see-logs class="reticle-flows-all" hidden>${SEE_LOGS_LABEL} →</button><button type="button" data-reticle-flows-all class="reticle-flows-all">${FLOWS_ALL_LABEL} →</button></span></div><div data-reticle-flow-strip class="reticle-flow-strip"></div></div>`;
|
|
184
|
+
export const CONTROLS_FOOT_HTML = `<div data-reticle-foot><div class="reticle-foot-stack"><div class="reticle-foot-workspace-row">${workspaceRowHtml()}${CHAT_VIEWS_HTML}</div></div></div>`;
|
|
154
185
|
function queryControlRefs(root) {
|
|
155
186
|
return {
|
|
156
187
|
pauseBtn: root.querySelector('[data-reticle-pause]') ?? undefined,
|
|
@@ -160,9 +191,21 @@ function queryControlRefs(root) {
|
|
|
160
191
|
exportBtn: root.querySelector('[data-reticle-export]') ?? undefined,
|
|
161
192
|
exportMsg: root.querySelector('[data-reticle-export-msg]') ?? undefined,
|
|
162
193
|
flows: root.querySelector('[data-reticle-flows]') ?? undefined,
|
|
194
|
+
allFlows: root.querySelector('[data-reticle-all-flows]') ?? undefined,
|
|
163
195
|
};
|
|
164
196
|
}
|
|
165
197
|
export class ControlPanel {
|
|
198
|
+
#recentPlays = (() => {
|
|
199
|
+
try {
|
|
200
|
+
const raw = JSON.parse(localStorage.getItem(RECENT_PLAYS_KEY) ?? '{}');
|
|
201
|
+
return 'object' === typeof raw && raw !== null && !Array.isArray(raw)
|
|
202
|
+
? raw
|
|
203
|
+
: {};
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
166
209
|
#refs = {
|
|
167
210
|
pauseBtn: undefined,
|
|
168
211
|
endBtn: undefined,
|
|
@@ -171,12 +214,15 @@ export class ControlPanel {
|
|
|
171
214
|
exportBtn: undefined,
|
|
172
215
|
exportMsg: undefined,
|
|
173
216
|
flows: undefined,
|
|
217
|
+
allFlows: undefined,
|
|
174
218
|
};
|
|
175
219
|
#state = SessionState.ACTIVE;
|
|
176
220
|
#fadeTimer;
|
|
177
221
|
#root;
|
|
178
222
|
#glow;
|
|
179
223
|
#flowItems = [];
|
|
224
|
+
#progress;
|
|
225
|
+
#progressTimer;
|
|
180
226
|
#workspaceTeardown;
|
|
181
227
|
#listeners;
|
|
182
228
|
#host;
|
|
@@ -198,18 +244,44 @@ export class ControlPanel {
|
|
|
198
244
|
}
|
|
199
245
|
this.#refs.pauseBtn?.addEventListener('click', () => this.#onPauseToggle(), { signal });
|
|
200
246
|
this.#refs.endBtn?.addEventListener('click', () => this.#onEnd(), { signal });
|
|
201
|
-
|
|
247
|
+
const onReplay = (e) => {
|
|
202
248
|
const target = e.target;
|
|
203
249
|
if (!(target instanceof HTMLElement))
|
|
204
250
|
return;
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
251
|
+
const button = target.closest('[data-reticle-replay]');
|
|
252
|
+
if (null === button || button.disabled)
|
|
253
|
+
return;
|
|
254
|
+
const name = button.getAttribute('data-reticle-replay');
|
|
255
|
+
if (name !== null && name.length > 0) {
|
|
256
|
+
this.#recentPlays[name] = Date.now();
|
|
257
|
+
try {
|
|
258
|
+
localStorage.setItem(RECENT_PLAYS_KEY, JSON.stringify(this.#recentPlays));
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
}
|
|
262
|
+
this.#renderFlows();
|
|
207
263
|
this.#host.emit(HumanControlKind.REPLAY, name);
|
|
208
264
|
}
|
|
209
|
-
}
|
|
265
|
+
};
|
|
266
|
+
for (const container of [this.#refs.flows, this.#refs.allFlows])
|
|
267
|
+
container?.addEventListener('click', onReplay, { signal });
|
|
268
|
+
this.#refs.flows?.querySelector('[data-reticle-flow-strip]')?.addEventListener('wheel', (e) => {
|
|
269
|
+
const strip = e.currentTarget;
|
|
270
|
+
if (0 !== e.deltaX || strip.scrollWidth <= strip.clientWidth)
|
|
271
|
+
return;
|
|
272
|
+
strip.scrollLeft += e.deltaY;
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
}, { signal, passive: false });
|
|
275
|
+
this.#refs.flows
|
|
276
|
+
?.querySelector('[data-reticle-see-logs]')
|
|
277
|
+
?.addEventListener('click', () => root.querySelector('[data-reticle-chat-view-btn="activity"]')?.click(), { signal });
|
|
278
|
+
this.#refs.flows
|
|
279
|
+
?.querySelector('[data-reticle-flows-all]')
|
|
280
|
+
?.addEventListener('click', () => root.querySelector('[data-reticle-chat-view-btn="flows"]')?.click(), { signal });
|
|
210
281
|
this.#refs.copyBtn?.addEventListener('click', () => this.#onCopy(), { signal });
|
|
211
282
|
this.#refs.exportBtn?.addEventListener('click', () => this.#onExport(), { signal });
|
|
212
283
|
this.#workspaceTeardown = mountWorkspaceSelector(root);
|
|
284
|
+
this.#renderFlows();
|
|
213
285
|
this.setState(SessionState.ACTIVE);
|
|
214
286
|
}
|
|
215
287
|
#runJson() {
|
|
@@ -264,6 +336,56 @@ export class ControlPanel {
|
|
|
264
336
|
this.#host.emit(HumanControlKind.END);
|
|
265
337
|
this.setState(SessionState.ENDED);
|
|
266
338
|
}
|
|
339
|
+
setFlowProgress(args) {
|
|
340
|
+
const progress = parseFlowProgress(args);
|
|
341
|
+
if (progress === undefined)
|
|
342
|
+
return;
|
|
343
|
+
this.#progress = progress;
|
|
344
|
+
if (this.#progressTimer !== undefined)
|
|
345
|
+
nativeClearTimeout(this.#progressTimer);
|
|
346
|
+
this.#progressTimer =
|
|
347
|
+
FlowProgressStatus.PLAYING === progress.status
|
|
348
|
+
? undefined
|
|
349
|
+
: nativeSetTimeout(() => {
|
|
350
|
+
this.#progress = undefined;
|
|
351
|
+
this.#paintProgress();
|
|
352
|
+
}, PROGRESS_LINGER_MS);
|
|
353
|
+
this.#paintProgress();
|
|
354
|
+
}
|
|
355
|
+
#paintProgress() {
|
|
356
|
+
const root = this.#refs.flows?.ownerDocument;
|
|
357
|
+
if (root === undefined)
|
|
358
|
+
return;
|
|
359
|
+
const progress = this.#progress;
|
|
360
|
+
const seeLogs = this.#refs.flows?.querySelector('[data-reticle-see-logs]');
|
|
361
|
+
if (seeLogs !== null && seeLogs !== undefined)
|
|
362
|
+
seeLogs.hidden = progress === undefined;
|
|
363
|
+
for (const container of [this.#refs.flows, this.#refs.allFlows]) {
|
|
364
|
+
for (const btn of container?.querySelectorAll('[data-reticle-replay]') ?? []) {
|
|
365
|
+
const name = btn.getAttribute('data-reticle-replay') ?? '';
|
|
366
|
+
const mine = progress !== undefined && progress.name === name;
|
|
367
|
+
if (!mine) {
|
|
368
|
+
btn.removeAttribute('data-state');
|
|
369
|
+
btn.style.removeProperty('--reticle-flow-progress');
|
|
370
|
+
if (btn.classList.contains('reticle-flow'))
|
|
371
|
+
btn.textContent = `▶ ${name}`;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const share = 0 < progress.total ? Math.round((100 * progress.done) / progress.total) : 4;
|
|
375
|
+
btn.setAttribute('data-state', progress.status);
|
|
376
|
+
btn.style.setProperty('--reticle-flow-progress', `${String(Math.max(4, share))}%`);
|
|
377
|
+
const glyph = PROGRESS_GLYPH[progress.status];
|
|
378
|
+
if (btn.classList.contains('reticle-flow'))
|
|
379
|
+
btn.textContent = `${glyph} ${name}`;
|
|
380
|
+
const play = btn.querySelector('.reticle-flow-play');
|
|
381
|
+
if (play !== null)
|
|
382
|
+
play.textContent = glyph;
|
|
383
|
+
const meta = btn.querySelector('.reticle-flow-meta');
|
|
384
|
+
if (meta !== null && 0 < progress.total)
|
|
385
|
+
meta.textContent = `Step ${String(progress.done)} of ${String(progress.total)}`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
267
389
|
setFlows(flows) {
|
|
268
390
|
const list = Array.isArray(flows) ? flows : [];
|
|
269
391
|
this.#flowItems = list
|
|
@@ -276,7 +398,12 @@ export class ControlPanel {
|
|
|
276
398
|
if (typeof name !== 'string' || 0 === name.length)
|
|
277
399
|
return null;
|
|
278
400
|
const start = rec['start'];
|
|
279
|
-
|
|
401
|
+
const createdAt = rec['createdAt'];
|
|
402
|
+
return {
|
|
403
|
+
name,
|
|
404
|
+
...('string' === typeof start && start.length > 0 ? { start } : {}),
|
|
405
|
+
...('number' === typeof createdAt && Number.isFinite(createdAt) ? { createdAt } : {}),
|
|
406
|
+
};
|
|
280
407
|
}
|
|
281
408
|
return null;
|
|
282
409
|
})
|
|
@@ -290,19 +417,72 @@ export class ControlPanel {
|
|
|
290
417
|
const el = this.#refs.flows;
|
|
291
418
|
if (el === undefined)
|
|
292
419
|
return;
|
|
420
|
+
const all = this.#refs.allFlows;
|
|
293
421
|
const doc = el.ownerDocument;
|
|
294
|
-
const testids = new Set(Array.from(doc.querySelectorAll(
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
422
|
+
const testids = new Set(Array.from(doc.querySelectorAll(testIdSelector())).map((n) => readTestId(n)));
|
|
423
|
+
const playable = (f) => f.start === undefined || testids.has(f.start);
|
|
424
|
+
const recent = [...this.#flowItems]
|
|
425
|
+
.filter(playable)
|
|
426
|
+
.sort((a, b) => (this.#recentPlays[b.name] ?? b.createdAt ?? 0) -
|
|
427
|
+
(this.#recentPlays[a.name] ?? a.createdAt ?? 0));
|
|
428
|
+
const strip = el.querySelector('[data-reticle-flow-strip]');
|
|
429
|
+
strip?.replaceChildren();
|
|
430
|
+
all?.replaceChildren();
|
|
431
|
+
const addButton = (container, flow, enabled) => {
|
|
298
432
|
const btn = doc.createElement('button');
|
|
299
433
|
btn.type = 'button';
|
|
300
434
|
btn.className = 'reticle-flow';
|
|
301
435
|
btn.setAttribute('data-reticle-replay', flow.name);
|
|
302
436
|
btn.textContent = `▶ ${flow.name}`;
|
|
303
|
-
|
|
437
|
+
btn.disabled = !enabled;
|
|
438
|
+
if (!enabled)
|
|
439
|
+
btn.title = 'Open the page where this flow starts to replay it';
|
|
440
|
+
container.appendChild(btn);
|
|
441
|
+
};
|
|
442
|
+
const addRow = (container, flow, enabled) => {
|
|
443
|
+
const btn = doc.createElement('button');
|
|
444
|
+
btn.type = 'button';
|
|
445
|
+
btn.className = 'reticle-flow-row';
|
|
446
|
+
btn.setAttribute('data-reticle-replay', flow.name);
|
|
447
|
+
btn.title = flow.name;
|
|
448
|
+
btn.disabled = !enabled;
|
|
449
|
+
const play = doc.createElement('span');
|
|
450
|
+
play.className = 'reticle-flow-play';
|
|
451
|
+
play.setAttribute('aria-hidden', 'true');
|
|
452
|
+
play.textContent = '▶';
|
|
453
|
+
const text = doc.createElement('span');
|
|
454
|
+
text.className = 'reticle-flow-text';
|
|
455
|
+
const name = doc.createElement('span');
|
|
456
|
+
name.className = 'reticle-flow-name';
|
|
457
|
+
name.textContent = flowTitle(flow.name);
|
|
458
|
+
const meta = doc.createElement('span');
|
|
459
|
+
meta.className = 'reticle-flow-meta';
|
|
460
|
+
meta.textContent = enabled ? FLOW_TEXT.REPLAYABLE : FLOW_TEXT.ELSEWHERE;
|
|
461
|
+
text.append(name, meta);
|
|
462
|
+
btn.append(play, text);
|
|
463
|
+
container.appendChild(btn);
|
|
464
|
+
};
|
|
465
|
+
for (const flow of recent.slice(0, 3))
|
|
466
|
+
if (strip !== null)
|
|
467
|
+
addButton(strip, flow, true);
|
|
468
|
+
el.setAttribute('data-has', recent.length > 0 ? '1' : '0');
|
|
469
|
+
if (all !== undefined) {
|
|
470
|
+
if (0 === this.#flowItems.length) {
|
|
471
|
+
const empty = doc.createElement('div');
|
|
472
|
+
empty.className = 'reticle-flows-empty';
|
|
473
|
+
const title = doc.createElement('strong');
|
|
474
|
+
title.textContent = FLOW_TEXT.EMPTY_TITLE;
|
|
475
|
+
const hint = doc.createElement('span');
|
|
476
|
+
hint.textContent = FLOW_TEXT.EMPTY_HINT;
|
|
477
|
+
empty.append(title, hint);
|
|
478
|
+
all.appendChild(empty);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
for (const flow of [...this.#flowItems].sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0)))
|
|
482
|
+
addRow(all, flow, playable(flow));
|
|
483
|
+
}
|
|
304
484
|
}
|
|
305
|
-
|
|
485
|
+
this.#paintProgress();
|
|
306
486
|
}
|
|
307
487
|
setState(state, text, tone) {
|
|
308
488
|
this.#state = state;
|
|
@@ -320,10 +500,10 @@ export class ControlPanel {
|
|
|
320
500
|
const ended = state === SessionState.ENDED;
|
|
321
501
|
if (refs.pauseBtn !== undefined) {
|
|
322
502
|
paintPauseBtn(refs.pauseBtn, state === SessionState.PAUSED);
|
|
323
|
-
refs.pauseBtn.
|
|
503
|
+
refs.pauseBtn.hidden = ended;
|
|
324
504
|
}
|
|
325
505
|
if (refs.endBtn !== undefined)
|
|
326
|
-
refs.endBtn.
|
|
506
|
+
refs.endBtn.hidden = ended;
|
|
327
507
|
if (refs.copyBtn !== undefined) {
|
|
328
508
|
if (ended)
|
|
329
509
|
refs.copyBtn.removeAttribute('hidden');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PRESENTER_DESIGN_TOKENS_CSS: string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const PRESENTER_DESIGN_TOKENS_CSS = `
|
|
2
|
+
[data-reticle-overlay]{
|
|
3
|
+
--reticle-hud-space-0:0px;
|
|
4
|
+
--reticle-hud-space-1:4px;
|
|
5
|
+
--reticle-hud-space-2:8px;
|
|
6
|
+
--reticle-hud-space-3:12px;
|
|
7
|
+
--reticle-hud-space-4:16px;
|
|
8
|
+
--reticle-hud-radius-sm:6px;
|
|
9
|
+
--reticle-hud-radius-md:10px;
|
|
10
|
+
--reticle-hud-radius-lg:14px;
|
|
11
|
+
--reticle-hud-radius-pill:9999px;
|
|
12
|
+
--reticle-hud-size-xs:10px;
|
|
13
|
+
--reticle-hud-size-sm:12px;
|
|
14
|
+
--reticle-hud-size-base:13px;
|
|
15
|
+
--reticle-hud-icon-size:18px;
|
|
16
|
+
--reticle-hud-control-size:36px;
|
|
17
|
+
--reticle-hud-control-gap:4px;
|
|
18
|
+
/* Compact: the HUD sits over the user's own app. 400×560 covered too much of it. */
|
|
19
|
+
--reticle-hud-dock-width:320px;
|
|
20
|
+
--reticle-hud-panel-height:440px;
|
|
21
|
+
--reticle-hud-ground:#0b0b0c;
|
|
22
|
+
--reticle-hud-surface:#151417;
|
|
23
|
+
--reticle-hud-inset:#232227;
|
|
24
|
+
--reticle-hud-hover:#1e1d22;
|
|
25
|
+
--reticle-hud-border:#29282d;
|
|
26
|
+
--reticle-hud-border-strong:#3a3941;
|
|
27
|
+
--reticle-hud-text:#f5f3ef;
|
|
28
|
+
--reticle-hud-text-muted:#a6a29a;
|
|
29
|
+
--reticle-hud-text-faint:#8b867d;
|
|
30
|
+
--reticle-hud-brand:#f0872e;
|
|
31
|
+
--reticle-hud-brand-soft:#2a1b0d;
|
|
32
|
+
--reticle-hud-ease:cubic-bezier(.4,0,.2,1);
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
@@ -5,7 +5,7 @@ const DOCK_PANEL_GAP_PX = 8;
|
|
|
5
5
|
const DOCK_SETTINGS_GAP_PX = 12;
|
|
6
6
|
const HUD_BAR_HEIGHT_PX = 44;
|
|
7
7
|
const CHAT_MIN_HEIGHT_PX = 160;
|
|
8
|
-
const CHAT_DEFAULT_MAX_HEIGHT_PX =
|
|
8
|
+
const CHAT_DEFAULT_MAX_HEIGHT_PX = 440;
|
|
9
9
|
const SETTINGS_DEFAULT_MAX_HEIGHT_PX = 520;
|
|
10
10
|
const SETTINGS_MIN_HEIGHT_PX = 200;
|
|
11
11
|
function unionRects(rects) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AccountState } from '@reticlehq/core';
|
|
2
|
+
export declare const RAIL_ATTR = "data-reticle-rail";
|
|
3
|
+
export declare const RAIL_SIGNIN_ATTR = "data-reticle-rail-signin";
|
|
4
|
+
export declare const RAIL_PROMO_ATTR = "data-reticle-rail-promo";
|
|
5
|
+
export declare const RAIL_TEXT: {
|
|
6
|
+
readonly TITLE: "Sign in to try Reticle Harness";
|
|
7
|
+
readonly DETAIL: "Free credits every month for autonomous end-to-end checks";
|
|
8
|
+
readonly CTA: "Sign in";
|
|
9
|
+
};
|
|
10
|
+
export declare const RAIL_HTML: string;
|
|
11
|
+
export declare function paintRail(root: HTMLElement, account: AccountState | undefined): void;
|
|
12
|
+
export declare const FRAME_CSS: string;
|