@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,75 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HUD_DRAG_IGNORE_SEL, HUD_DRAG_THRESHOLD_PX, MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR, } from './presenter-config.js';
|
|
2
2
|
import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
|
|
3
|
+
import { applyHudPosition, clampHudPosition, hudLayoutBox, relayoutHudPosition, } from './presenter-hud-position.js';
|
|
4
|
+
export { applyHudPosition, isHudDragged, readHudPosition, relayoutHudPosition, clampHudPosition, hudLayoutBox, resetHudDockPosition, } from './presenter-hud-position.js';
|
|
3
5
|
const DRAG_HANDLE_DRAGGING_CLASS = 'reticle-drag-handle--dragging';
|
|
4
|
-
/** Whether the HUD is off the default bottom-right dock. */
|
|
5
|
-
export function isHudDragged(hud) {
|
|
6
|
-
return '1' === hud.getAttribute(HUD_DRAGGED_ATTR);
|
|
7
|
-
}
|
|
8
|
-
/** Clamp a HUD position so the full panel stays inside the viewport. */
|
|
9
|
-
export function clampHudPosition(left, top, width, height, viewportWidth, viewportHeight, margin = HUD_DOCK_MARGIN_PX) {
|
|
10
|
-
const minLeft = margin;
|
|
11
|
-
const minTop = margin;
|
|
12
|
-
const maxLeft = Math.max(minLeft, viewportWidth - width - margin);
|
|
13
|
-
const maxTop = Math.max(minTop, viewportHeight - height - margin);
|
|
14
|
-
return {
|
|
15
|
-
left: Math.max(minLeft, Math.min(left, maxLeft)),
|
|
16
|
-
top: Math.max(minTop, Math.min(top, maxTop)),
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/** Paint an explicit left/top position (switches the HUD off the default bottom-right dock). */
|
|
20
|
-
export function applyHudPosition(hud, left, top) {
|
|
21
|
-
hud.setAttribute(HUD_DRAGGED_ATTR, '1');
|
|
22
|
-
hud.style.setProperty(HUD_POS_X_VAR, `${String(left)}px`);
|
|
23
|
-
hud.style.setProperty(HUD_POS_Y_VAR, `${String(top)}px`);
|
|
24
|
-
}
|
|
25
|
-
/** Return the HUD to the default bottom-right dock. */
|
|
26
|
-
export function resetHudDockPosition(hud) {
|
|
27
|
-
hud.removeAttribute(HUD_DRAGGED_ATTR);
|
|
28
|
-
hud.style.removeProperty(HUD_POS_X_VAR);
|
|
29
|
-
hud.style.removeProperty(HUD_POS_Y_VAR);
|
|
30
|
-
}
|
|
31
|
-
/** Return the HUD's laid-out box (rounded to whole pixels for stable clamping). */
|
|
32
|
-
export function hudLayoutBox(hud) {
|
|
33
|
-
const rect = hud.getBoundingClientRect();
|
|
34
|
-
return {
|
|
35
|
-
left: rect.left,
|
|
36
|
-
top: rect.top,
|
|
37
|
-
width: rect.width,
|
|
38
|
-
height: rect.height,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/** Read the dragged HUD's authored position (CSS vars), falling back to layout box. */
|
|
42
|
-
export function readHudPosition(hud) {
|
|
43
|
-
const x = hud.style.getPropertyValue(HUD_POS_X_VAR);
|
|
44
|
-
const y = hud.style.getPropertyValue(HUD_POS_Y_VAR);
|
|
45
|
-
if (x !== '' && y !== '') {
|
|
46
|
-
return { left: Number.parseFloat(x), top: Number.parseFloat(y) };
|
|
47
|
-
}
|
|
48
|
-
const rect = hud.getBoundingClientRect();
|
|
49
|
-
return { left: rect.left, top: rect.top };
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Re-clamp a dragged HUD after resize, minimise/expand, or any layout-driven size change.
|
|
53
|
-
* No-op when the panel is still on the default dock.
|
|
54
|
-
*/
|
|
55
|
-
export function relayoutHudPosition(hud) {
|
|
56
|
-
if (!isHudDragged(hud))
|
|
57
|
-
return;
|
|
58
|
-
const { left, top } = readHudPosition(hud);
|
|
59
|
-
const { width, height } = hudLayoutBox(hud);
|
|
60
|
-
const next = clampHudPosition(left, top, width, height, window.innerWidth, window.innerHeight);
|
|
61
|
-
applyHudPosition(hud, next.left, next.top);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Keep a dragged HUD inside the viewport when the window or panel size changes
|
|
65
|
-
* (minimise ↔ expand, tally appearing, viewport resize).
|
|
66
|
-
*/
|
|
67
6
|
export function installHudPositionGuards(hud, overlay) {
|
|
68
7
|
const scheduleRelayout = () => {
|
|
69
8
|
scheduleSyncDockLayout(hud, overlay);
|
|
70
9
|
};
|
|
71
|
-
// One signal for all three listeners here. The observers below are NOT covered — neither
|
|
72
|
-
// ResizeObserver nor MutationObserver accepts one — so they keep their explicit disconnects.
|
|
73
10
|
const listeners = new AbortController();
|
|
74
11
|
const { signal } = listeners;
|
|
75
12
|
const onResize = () => scheduleRelayout();
|
|
@@ -93,10 +30,6 @@ export function installHudPositionGuards(hud, overlay) {
|
|
|
93
30
|
minObserver.disconnect();
|
|
94
31
|
};
|
|
95
32
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Drag the presenter HUD by its header. Returns a teardown that removes listeners.
|
|
98
|
-
* Uses pointer capture so the drag stays smooth even when the cursor leaves the head.
|
|
99
|
-
*/
|
|
100
33
|
export function installHudDrag(hud, head, callbacks = {}) {
|
|
101
34
|
let dragging = false;
|
|
102
35
|
let startX = 0;
|
|
@@ -173,8 +106,6 @@ export function installHudDrag(hud, head, callbacks = {}) {
|
|
|
173
106
|
releaseCapture(e.pointerId);
|
|
174
107
|
finishDrag();
|
|
175
108
|
};
|
|
176
|
-
// All four pointer phases share one signal: they are added together and must come off
|
|
177
|
-
// together, and a partial removal would leave a half-live drag.
|
|
178
109
|
const listeners = new AbortController();
|
|
179
110
|
const { signal } = listeners;
|
|
180
111
|
head.addEventListener('pointerdown', onPointerDown, { signal });
|
|
@@ -188,10 +119,6 @@ export function installHudDrag(hud, head, callbacks = {}) {
|
|
|
188
119
|
finishDrag();
|
|
189
120
|
};
|
|
190
121
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Drag the presenter HUD from multiple handles (FAB when collapsed, toolbar brand when expanded).
|
|
193
|
-
* Returns a teardown that removes listeners from every handle.
|
|
194
|
-
*/
|
|
195
122
|
export function installHudDragHandles(hud, handles, callbacks = {}) {
|
|
196
123
|
const teardowns = handles.map((handle) => installHudDrag(hud, handle, callbacks));
|
|
197
124
|
return () => {
|
|
@@ -1,42 +1,21 @@
|
|
|
1
1
|
import { PresenterMode } from '@reticlehq/core';
|
|
2
2
|
import { GlowPhase, BorderMode } from './presenter-config.js';
|
|
3
|
-
/** What the controller needs from the Presenter: the clock, the timing knobs, and a setMode callback. */
|
|
4
3
|
interface GlowDeps {
|
|
5
4
|
now: () => number;
|
|
6
5
|
idleAfterMs: number;
|
|
7
6
|
glowFadeMs: number;
|
|
8
7
|
borderMode: BorderMode;
|
|
9
|
-
/** Called when the glow fades to idle, so the Presenter clears the READING/ACTING chip. */
|
|
10
8
|
setMode: (mode: PresenterMode) => void;
|
|
11
9
|
}
|
|
12
|
-
/**
|
|
13
|
-
* The glow / activity state machine - drives the border shimmer + synthetic-cursor visibility from
|
|
14
|
-
* agent-activity timing. Extracted from presenter.ts so the controller file stays under the size cap;
|
|
15
|
-
* behavior is byte-for-byte the same. It holds references to the same `#glow`/`#cursor` nodes the
|
|
16
|
-
* Presenter does (both call setAttribute on those elements - the Presenter owns the SESSION border via
|
|
17
|
-
* data-on; this owns the BUSY shimmer via data-busy). A burst flips the glow IN once, holds it (the
|
|
18
|
-
* slow reticle-pulse keeps breathing - no per-action restart/strobe), then fades it OUT once after a quiet
|
|
19
|
-
* window.
|
|
20
|
-
*/
|
|
21
10
|
export declare class GlowController {
|
|
22
11
|
#private;
|
|
23
12
|
constructor(deps: GlowDeps);
|
|
24
|
-
/** Wire the glow + cursor elements after the Presenter mounts the DOM. */
|
|
25
13
|
setElements(glow: HTMLElement | undefined, cursor: HTMLElement | undefined): void;
|
|
26
|
-
/** Current glow phase (test/diagnostic accessor). */
|
|
27
14
|
phase(): GlowPhase;
|
|
28
|
-
/** Last activity timestamp - read by the Presenter's liveness heartbeat. */
|
|
29
15
|
lastActivityMs(): number;
|
|
30
|
-
/** Set the activity baseline WITHOUT entering busy (sessionStart / revive). */
|
|
31
16
|
resetActivity(ms: number): void;
|
|
32
|
-
/**
|
|
33
|
-
* Record agent activity. Idempotent while busy - only the first activity from idle/fading flips the
|
|
34
|
-
* glow on (no strobe). `ms` lets log read the clock exactly once per row.
|
|
35
|
-
*/
|
|
36
17
|
markActivity(ms?: number): void;
|
|
37
|
-
/** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
|
|
38
18
|
scheduleIdle(): void;
|
|
39
|
-
/** Clear both timers (called from Presenter.destroy). */
|
|
40
19
|
teardown(): void;
|
|
41
20
|
}
|
|
42
21
|
export {};
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { PresenterMode } from '@reticlehq/core';
|
|
2
|
-
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
2
|
+
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
|
|
3
3
|
import { GlowPhase, BorderMode, DATA_BUSY, BUSY_ON, BUSY_OFF, GLOW_ON, GLOW_OFF, DATA_ON, } from './presenter-config.js';
|
|
4
|
-
/**
|
|
5
|
-
* The glow / activity state machine - drives the border shimmer + synthetic-cursor visibility from
|
|
6
|
-
* agent-activity timing. Extracted from presenter.ts so the controller file stays under the size cap;
|
|
7
|
-
* behavior is byte-for-byte the same. It holds references to the same `#glow`/`#cursor` nodes the
|
|
8
|
-
* Presenter does (both call setAttribute on those elements - the Presenter owns the SESSION border via
|
|
9
|
-
* data-on; this owns the BUSY shimmer via data-busy). A burst flips the glow IN once, holds it (the
|
|
10
|
-
* slow reticle-pulse keeps breathing - no per-action restart/strobe), then fades it OUT once after a quiet
|
|
11
|
-
* window.
|
|
12
|
-
*/
|
|
13
4
|
export class GlowController {
|
|
14
5
|
#phase = GlowPhase.IDLE;
|
|
15
6
|
#lastActivityMs = 0;
|
|
@@ -29,38 +20,28 @@ export class GlowController {
|
|
|
29
20
|
this.#borderMode = deps.borderMode;
|
|
30
21
|
this.#setMode = deps.setMode;
|
|
31
22
|
}
|
|
32
|
-
/** Wire the glow + cursor elements after the Presenter mounts the DOM. */
|
|
33
23
|
setElements(glow, cursor) {
|
|
34
24
|
this.#glow = glow;
|
|
35
25
|
this.#cursor = cursor;
|
|
36
26
|
}
|
|
37
|
-
/** Current glow phase (test/diagnostic accessor). */
|
|
38
27
|
phase() {
|
|
39
28
|
return this.#phase;
|
|
40
29
|
}
|
|
41
|
-
/** Last activity timestamp - read by the Presenter's liveness heartbeat. */
|
|
42
30
|
lastActivityMs() {
|
|
43
31
|
return this.#lastActivityMs;
|
|
44
32
|
}
|
|
45
|
-
/** Set the activity baseline WITHOUT entering busy (sessionStart / revive). */
|
|
46
33
|
resetActivity(ms) {
|
|
47
34
|
this.#lastActivityMs = ms;
|
|
48
35
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Record agent activity. Idempotent while busy - only the first activity from idle/fading flips the
|
|
51
|
-
* glow on (no strobe). `ms` lets log read the clock exactly once per row.
|
|
52
|
-
*/
|
|
53
36
|
markActivity(ms = this.#now()) {
|
|
54
37
|
this.#lastActivityMs = ms;
|
|
55
38
|
if (this.#phase === GlowPhase.IDLE || this.#phase === GlowPhase.FADING)
|
|
56
39
|
this.#enterBusy();
|
|
57
40
|
this.#armIdleCheck();
|
|
58
41
|
}
|
|
59
|
-
/** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
|
|
60
42
|
scheduleIdle() {
|
|
61
43
|
this.#armIdleCheck();
|
|
62
44
|
}
|
|
63
|
-
/** Clear both timers (called from Presenter.destroy). */
|
|
64
45
|
teardown() {
|
|
65
46
|
if (this.#idleCheckTimer !== undefined)
|
|
66
47
|
nativeClearTimeout(this.#idleCheckTimer);
|
|
@@ -76,14 +57,11 @@ export class GlowController {
|
|
|
76
57
|
}
|
|
77
58
|
this.#phase = GlowPhase.BUSY;
|
|
78
59
|
if (this.#borderMode === BorderMode.SESSION) {
|
|
79
|
-
// Session mode: modulate only the shimmer; the base border (data-on) stays session-owned.
|
|
80
60
|
this.#glow?.setAttribute(DATA_BUSY, BUSY_ON);
|
|
81
61
|
}
|
|
82
62
|
else {
|
|
83
|
-
// back-compat: the busy machine owns the base border.
|
|
84
63
|
this.#glow?.setAttribute(DATA_ON, GLOW_ON);
|
|
85
64
|
}
|
|
86
|
-
// The HUD/log is session-owned (sessionStart/End), NOT toggled here - it stays put on idle.
|
|
87
65
|
this.#cursor?.setAttribute(DATA_ON, GLOW_ON);
|
|
88
66
|
}
|
|
89
67
|
#armIdleCheck() {
|
|
@@ -97,7 +75,6 @@ export class GlowController {
|
|
|
97
75
|
return;
|
|
98
76
|
const quietFor = this.#now() - this.#lastActivityMs;
|
|
99
77
|
if (quietFor < this.#idleAfterMs) {
|
|
100
|
-
// Activity landed during the wait; re-arm for only the remaining quiet time.
|
|
101
78
|
this.#idleCheckTimer = nativeSetTimeout(() => this.#checkIdle(), this.#idleAfterMs - quietFor);
|
|
102
79
|
return;
|
|
103
80
|
}
|
|
@@ -106,16 +83,13 @@ export class GlowController {
|
|
|
106
83
|
#beginFade() {
|
|
107
84
|
this.#phase = GlowPhase.FADING;
|
|
108
85
|
if (this.#borderMode === BorderMode.SESSION) {
|
|
109
|
-
// Session mode: relax the shimmer only; the base border stays on.
|
|
110
86
|
this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
|
|
111
87
|
}
|
|
112
88
|
else {
|
|
113
|
-
// back-compat: fade the base border out.
|
|
114
89
|
this.#glow?.setAttribute(DATA_ON, GLOW_OFF);
|
|
115
90
|
}
|
|
116
|
-
// Do NOT hide the HUD/log on idle - it persists for the whole session (sessionStart/End).
|
|
117
91
|
this.#cursor?.setAttribute(DATA_ON, GLOW_OFF);
|
|
118
|
-
this.#setMode(PresenterMode.IDLE);
|
|
92
|
+
this.#setMode(PresenterMode.IDLE);
|
|
119
93
|
this.#fadeTimer = nativeSetTimeout(() => {
|
|
120
94
|
this.#fadeTimer = undefined;
|
|
121
95
|
if (this.#phase === GlowPhase.FADING)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function isHudDragged(hud: HTMLElement): boolean;
|
|
2
|
+
export declare function clampHudPosition(left: number, top: number, width: number, height: number, viewportWidth: number, viewportHeight: number, margin?: number): {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function applyHudPosition(hud: HTMLElement, left: number, top: number): void;
|
|
7
|
+
export declare function resetHudDockPosition(hud: HTMLElement): void;
|
|
8
|
+
export declare function hudLayoutBox(hud: HTMLElement): {
|
|
9
|
+
left: number;
|
|
10
|
+
top: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function readHudPosition(hud: HTMLElement): {
|
|
15
|
+
left: number;
|
|
16
|
+
top: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function relayoutHudPosition(hud: HTMLElement): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { HUD_DOCK_MARGIN_PX, HUD_DRAGGED_ATTR, HUD_POS_X_VAR, HUD_POS_Y_VAR, } from './presenter-config.js';
|
|
2
|
+
export function isHudDragged(hud) {
|
|
3
|
+
return '1' === hud.getAttribute(HUD_DRAGGED_ATTR);
|
|
4
|
+
}
|
|
5
|
+
export function clampHudPosition(left, top, width, height, viewportWidth, viewportHeight, margin = HUD_DOCK_MARGIN_PX) {
|
|
6
|
+
const minLeft = margin;
|
|
7
|
+
const minTop = margin;
|
|
8
|
+
const maxLeft = Math.max(minLeft, viewportWidth - width - margin);
|
|
9
|
+
const maxTop = Math.max(minTop, viewportHeight - height - margin);
|
|
10
|
+
return {
|
|
11
|
+
left: Math.max(minLeft, Math.min(left, maxLeft)),
|
|
12
|
+
top: Math.max(minTop, Math.min(top, maxTop)),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function applyHudPosition(hud, left, top) {
|
|
16
|
+
hud.setAttribute(HUD_DRAGGED_ATTR, '1');
|
|
17
|
+
hud.style.setProperty(HUD_POS_X_VAR, `${String(left)}px`);
|
|
18
|
+
hud.style.setProperty(HUD_POS_Y_VAR, `${String(top)}px`);
|
|
19
|
+
}
|
|
20
|
+
export function resetHudDockPosition(hud) {
|
|
21
|
+
hud.removeAttribute(HUD_DRAGGED_ATTR);
|
|
22
|
+
hud.style.removeProperty(HUD_POS_X_VAR);
|
|
23
|
+
hud.style.removeProperty(HUD_POS_Y_VAR);
|
|
24
|
+
}
|
|
25
|
+
export function hudLayoutBox(hud) {
|
|
26
|
+
const rect = hud.getBoundingClientRect();
|
|
27
|
+
return {
|
|
28
|
+
left: rect.left,
|
|
29
|
+
top: rect.top,
|
|
30
|
+
width: rect.width,
|
|
31
|
+
height: rect.height,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function readHudPosition(hud) {
|
|
35
|
+
const x = hud.style.getPropertyValue(HUD_POS_X_VAR);
|
|
36
|
+
const y = hud.style.getPropertyValue(HUD_POS_Y_VAR);
|
|
37
|
+
if (x !== '' && y !== '') {
|
|
38
|
+
return { left: Number.parseFloat(x), top: Number.parseFloat(y) };
|
|
39
|
+
}
|
|
40
|
+
const rect = hud.getBoundingClientRect();
|
|
41
|
+
return { left: rect.left, top: rect.top };
|
|
42
|
+
}
|
|
43
|
+
export function relayoutHudPosition(hud) {
|
|
44
|
+
if (!isHudDragged(hud))
|
|
45
|
+
return;
|
|
46
|
+
const { left, top } = readHudPosition(hud);
|
|
47
|
+
const { width, height } = hudLayoutBox(hud);
|
|
48
|
+
const next = clampHudPosition(left, top, width, height, window.innerWidth, window.innerHeight);
|
|
49
|
+
applyHudPosition(hud, next.left, next.top);
|
|
50
|
+
}
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The impact report's surface.
|
|
3
|
-
*
|
|
4
|
-
* Same glass as the chat panel and anchored in the same slot above the toolbar, because it is the
|
|
5
|
-
* same object seen a different way: what this session is doing, and what every session has done.
|
|
6
|
-
*/
|
|
7
1
|
export declare const REPORT_CSS: string;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import { HUD_DROP_SHADOW } from './presenter-hud-chrome.js';
|
|
1
|
+
import { HUD_DROP_SHADOW } from './chrome/presenter-hud-chrome.js';
|
|
2
2
|
import { DOCK_ALIGN_ATTR, DOCK_ATTR, REPORT_ATTR, REPORT_PANEL_ATTR } from './presenter-config.js';
|
|
3
3
|
const OVERLAY = 'data-reticle-overlay';
|
|
4
|
-
/**
|
|
5
|
-
* The impact report's surface.
|
|
6
|
-
*
|
|
7
|
-
* Same glass as the chat panel and anchored in the same slot above the toolbar, because it is the
|
|
8
|
-
* same object seen a different way: what this session is doing, and what every session has done.
|
|
9
|
-
*/
|
|
10
4
|
export const REPORT_CSS = `
|
|
11
5
|
[${REPORT_PANEL_ATTR}]{
|
|
12
6
|
position:absolute;right:0;left:auto;bottom:calc(100% + 12px);z-index:31;
|
|
@@ -88,6 +82,12 @@ export const REPORT_CSS = `
|
|
|
88
82
|
[${REPORT_PANEL_ATTR}] .reticle-report-defect-source{
|
|
89
83
|
color:var(--reticle-faint);font-size:9.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
90
84
|
overflow-wrap:anywhere;}
|
|
85
|
+
.reticle-report-defect-link{flex:none;display:inline-flex;align-items:center;justify-content:center;
|
|
86
|
+
width:20px;height:20px;border-radius:6px;color:var(--reticle-faint);pointer-events:auto;
|
|
87
|
+
opacity:0;transition:opacity .12s ease,color .12s ease,background .12s ease;}
|
|
88
|
+
.reticle-report-defect:hover .reticle-report-defect-link,
|
|
89
|
+
.reticle-report-defect-link:focus-visible{opacity:1;}
|
|
90
|
+
.reticle-report-defect-link:hover{color:var(--reticle-fg);background:rgba(255,255,255,.07);}
|
|
91
91
|
[${REPORT_PANEL_ATTR}] .reticle-report-defects-more{
|
|
92
92
|
display:inline-block;margin-top:8px;font-size:10px;color:var(--reticle-c-active);
|
|
93
93
|
text-decoration:none;}
|
|
@@ -123,4 +123,19 @@ export const REPORT_CSS = `
|
|
|
123
123
|
[${REPORT_PANEL_ATTR}] .reticle-report-links a{
|
|
124
124
|
color:var(--reticle-faint);font-size:10.5px;text-decoration:none;}
|
|
125
125
|
[${REPORT_PANEL_ATTR}] .reticle-report-links a:hover{color:var(--reticle-fg);text-decoration:underline;}
|
|
126
|
+
|
|
127
|
+
/* Who this record belongs to, and the push-now control. Tokens only — no literal colours. */
|
|
128
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-identity{
|
|
129
|
+
display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:10px 14px 0;min-height:0;}
|
|
130
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-identity:empty{display:none;}
|
|
131
|
+
[${REPORT_PANEL_ATTR}] .reticle-sync-now{
|
|
132
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
133
|
+
border:none;background:transparent;color:var(--reticle-faint);cursor:pointer;line-height:0;padding:2px;}
|
|
134
|
+
[${REPORT_PANEL_ATTR}] .reticle-sync-now:hover{color:var(--reticle-fg);}
|
|
135
|
+
/* The one moment this control has an opinion: it says it is going, not that it has arrived. */
|
|
136
|
+
[${REPORT_PANEL_ATTR}] .reticle-sync-now[data-syncing="1"]{
|
|
137
|
+
color:var(--reticle-c-active);animation:reticle-sync-spin 900ms linear infinite;}
|
|
138
|
+
@keyframes reticle-sync-spin{to{transform:rotate(360deg);}}
|
|
139
|
+
@media (prefers-reduced-motion:reduce){
|
|
140
|
+
[${REPORT_PANEL_ATTR}] .reticle-sync-now[data-syncing="1"]{animation:none;}}
|
|
126
141
|
`;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
|
|
1
|
+
import { type AccountState, type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
|
|
2
2
|
export declare function reportPanelHtml(): string;
|
|
3
|
-
export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string): string;
|
|
4
|
-
|
|
5
|
-
/** Opened from the toolbar and from the chat, so the shell decides what else must close. */
|
|
3
|
+
export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState, projectName?: string): string;
|
|
4
|
+
interface ReportHost {
|
|
6
5
|
onBeforeOpen?: () => void;
|
|
6
|
+
onSyncNow?: () => void;
|
|
7
7
|
}
|
|
8
|
-
/** The report panel controller: scope toggle, live repaint, share + referral actions. */
|
|
9
8
|
export declare class PresenterReport {
|
|
10
9
|
#private;
|
|
11
10
|
constructor(host?: ReportHost);
|
|
12
11
|
mount(root: HTMLElement): void;
|
|
13
|
-
/** The record arrived from the daemon. Repaint only if the panel is on screen. */
|
|
14
12
|
setSnapshot(snapshot: ImpactSnapshot): void;
|
|
15
13
|
snapshot(): ImpactSnapshot | undefined;
|
|
16
14
|
isOpen(): boolean;
|
|
@@ -18,6 +16,6 @@ export declare class PresenterReport {
|
|
|
18
16
|
close(): void;
|
|
19
17
|
toggle(): void;
|
|
20
18
|
contains(node: Node): boolean;
|
|
21
|
-
/** The post text for whichever scope is showing. */
|
|
22
19
|
shareText(): string;
|
|
23
20
|
}
|
|
21
|
+
export {};
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { IMPACT_DEFECT_LIMIT } from '@reticlehq/core';
|
|
2
|
-
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './presenter-icons.js';
|
|
3
|
-
import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
|
|
1
|
+
import { IMPACT_DEFECT_LIMIT, } from '@reticlehq/core';
|
|
2
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './icons/presenter-icons.js';
|
|
3
|
+
import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
|
|
4
|
+
import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
|
|
5
|
+
import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, SYNC_BTN_ATTR, accountControlHtml, syncButtonHtml, } from './presenter-account.js';
|
|
4
6
|
import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
|
|
5
|
-
import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './presenter-report-copy.js';
|
|
6
|
-
/**
|
|
7
|
-
* The impact report: what Reticle has actually done for this user.
|
|
8
|
-
*
|
|
9
|
-
* Reads the local record only - the panel is a view over `.reticle/impact.json`, pushed by the
|
|
10
|
-
* daemon, never fetched from us. Two scopes (this project, this machine) behind one toggle.
|
|
11
|
-
*/
|
|
7
|
+
import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './chrome/presenter-report-copy.js';
|
|
12
8
|
const SCOPE_ATTR = 'data-reticle-report-scope';
|
|
13
9
|
const SHARE_X_ATTR = 'data-reticle-share-x';
|
|
14
10
|
const SHARE_IN_ATTR = 'data-reticle-share-in';
|
|
@@ -17,7 +13,7 @@ const REFER_ATTR = 'data-reticle-refer';
|
|
|
17
13
|
const COPIED_FLASH_MS = 1600;
|
|
18
14
|
export function reportPanelHtml() {
|
|
19
15
|
const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
|
|
20
|
-
return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="
|
|
16
|
+
return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="region" aria-label="Reticle impact" aria-hidden="true">
|
|
21
17
|
<div class="reticle-report-inner">
|
|
22
18
|
<div class="reticle-report-head">
|
|
23
19
|
<span class="reticle-report-title">${REPORT_TEXT.TITLE}</span>
|
|
@@ -40,19 +36,12 @@ export function reportPanelHtml() {
|
|
|
40
36
|
</div>
|
|
41
37
|
</div>`;
|
|
42
38
|
}
|
|
43
|
-
/** A stat card: the number, what it is, and - for an estimate - what it is measured against. */
|
|
44
39
|
function card(value, label, basis) {
|
|
45
40
|
const tag = basis === undefined
|
|
46
41
|
? ''
|
|
47
42
|
: `<span class="reticle-report-basis" title="${basis}">${REPORT_TEXT.ESTIMATE_TAG}</span>`;
|
|
48
43
|
return `<div class="reticle-report-card"><span class="reticle-report-value">${value}</span><span class="reticle-report-label">${label}${tag}</span></div>`;
|
|
49
44
|
}
|
|
50
|
-
/**
|
|
51
|
-
* A 30-day bar chart of verdicts, as inline spans.
|
|
52
|
-
*
|
|
53
|
-
* Bars rather than a line: a day with no verdicts is a real fact about the week, and a line chart
|
|
54
|
-
* interpolates straight through it.
|
|
55
|
-
*/
|
|
56
45
|
function chart(scope) {
|
|
57
46
|
const days = scope.days.slice(-30);
|
|
58
47
|
if (0 === days.length)
|
|
@@ -68,61 +57,14 @@ function chart(scope) {
|
|
|
68
57
|
.join('');
|
|
69
58
|
return `<div class="reticle-report-chart-wrap"><span class="reticle-report-section">${REPORT_TEXT.CHART}</span><div class="reticle-report-chart">${bars}</div></div>`;
|
|
70
59
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Escape text that came from the app under test.
|
|
73
|
-
*
|
|
74
|
-
* A defect title is an element's accessible name or a verdict's failure reason — both of which are
|
|
75
|
-
* ultimately the CONTENT of somebody else's page, and this panel builds its DOM from an HTML string.
|
|
76
|
-
* Everything else the report renders is a number or a date; this is the first app-derived text to
|
|
77
|
-
* reach it, so the escaping arrives with it.
|
|
78
|
-
*/
|
|
79
|
-
function esc(value) {
|
|
80
|
-
return value
|
|
81
|
-
.replace(/&/g, '&')
|
|
82
|
-
.replace(/</g, '<')
|
|
83
|
-
.replace(/>/g, '>')
|
|
84
|
-
.replace(/"/g, '"');
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* The short list of what is currently broken.
|
|
88
|
-
*
|
|
89
|
-
* The hero number already says HOW MANY defects Reticle caught; this says WHICH ONES, which is the
|
|
90
|
-
* difference between a statistic and something a person can act on. Deliberately short: a panel in
|
|
91
|
-
* the corner of somebody's app is not a triage queue, and the link at the bottom is where the queue
|
|
92
|
-
* lives. When the project is not linked to a workspace there is no link and no nagging — the free
|
|
93
|
-
* tool is complete on its own.
|
|
94
|
-
*/
|
|
95
|
-
/**
|
|
96
|
-
* Whether a dashboard link is safe to render as a clickable href.
|
|
97
|
-
*
|
|
98
|
-
* The url arrives from `.reticle/cloud.json` — a file in somebody's repository, which means it is
|
|
99
|
-
* INPUT. Escaping the quotes stops it breaking out of the attribute but does nothing about the
|
|
100
|
-
* SCHEME, so `javascript:...` produced a link that ran code inside the developer's own application,
|
|
101
|
-
* from a panel Reticle injected there. Only the two schemes a dashboard can actually live on are
|
|
102
|
-
* allowed; anything else renders no link at all, which is the same state as an unlinked project and
|
|
103
|
-
* therefore already a supported one.
|
|
104
|
-
*/
|
|
105
|
-
function isSafeDashboardUrl(raw) {
|
|
106
|
-
try {
|
|
107
|
-
const scheme = new URL(raw).protocol;
|
|
108
|
-
return 'https:' === scheme || 'http:' === scheme;
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// Not a url at all. A relative path cannot address a dashboard on another origin, so there is
|
|
112
|
-
// nothing to render and nothing lost by refusing it.
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
60
|
function defects(scope, dashboardUrl) {
|
|
117
|
-
/*
|
|
118
|
-
* Defaulted here as well as in the schema. Zod's default applies when a record is PARSED, and the
|
|
119
|
-
* snapshot reaching this panel is pushed straight from the daemon rather than round-tripped
|
|
120
|
-
* through the schema — so a record written by an older build arrives with no `defects` field at
|
|
121
|
-
* all and used to throw, taking the whole report down with it.
|
|
122
|
-
*/
|
|
123
61
|
const list = (scope.defects ?? []).slice(0, IMPACT_DEFECT_LIMIT);
|
|
124
62
|
if (0 === list.length)
|
|
125
63
|
return '';
|
|
64
|
+
const linked = dashboardUrl !== undefined && isSafeDashboardUrl(dashboardUrl);
|
|
65
|
+
const rowLink = linked
|
|
66
|
+
? `<a class="reticle-report-defect-link" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener" title="${REPORT_TEXT.DEFECT_LINK_TITLE}" aria-label="${REPORT_TEXT.DEFECT_LINK_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.SEND)}</a>`
|
|
67
|
+
: '';
|
|
126
68
|
const rows = list
|
|
127
69
|
.map((d) => {
|
|
128
70
|
const detail = d.detail === undefined
|
|
@@ -131,17 +73,15 @@ function defects(scope, dashboardUrl) {
|
|
|
131
73
|
const source = d.source === undefined
|
|
132
74
|
? ''
|
|
133
75
|
: `<span class="reticle-report-defect-source">${esc(d.source)}</span>`;
|
|
134
|
-
return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}</li>`;
|
|
76
|
+
return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}${rowLink}</li>`;
|
|
135
77
|
})
|
|
136
78
|
.join('');
|
|
137
|
-
|
|
138
|
-
// this list is the recent tail of it.
|
|
139
|
-
const more = dashboardUrl === undefined || !isSafeDashboardUrl(dashboardUrl)
|
|
79
|
+
const more = !linked
|
|
140
80
|
? ''
|
|
141
81
|
: `<a class="reticle-report-defects-more" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener">${REPORT_TEXT.DEFECTS_MORE}${scope.counts.failed > list.length ? ` (${String(scope.counts.failed)})` : ''}</a>`;
|
|
142
82
|
return `<div class="reticle-report-defects-wrap"><span class="reticle-report-section">${REPORT_TEXT.DEFECTS}</span><ul class="reticle-report-defects">${rows}</ul>${more}</div>`;
|
|
143
83
|
}
|
|
144
|
-
export function reportBodyHtml(scope, dashboardUrl) {
|
|
84
|
+
export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
|
|
145
85
|
const c = scope.counts;
|
|
146
86
|
if (0 === c.calls)
|
|
147
87
|
return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
|
|
@@ -164,27 +104,23 @@ export function reportBodyHtml(scope, dashboardUrl) {
|
|
|
164
104
|
card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
|
|
165
105
|
card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
|
|
166
106
|
].join('');
|
|
167
|
-
|
|
107
|
+
const identity = `<div class="reticle-report-identity">${accountControlHtml(account, { dashboardUrl, projectName, verdicts: c.verdicts, defects: c.failed })}${syncButtonHtml(dashboardUrl)}</div>`;
|
|
108
|
+
return `${identity}${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
|
|
168
109
|
}
|
|
169
|
-
|
|
170
|
-
* The one line an UNLINKED user is shown about the dashboard.
|
|
171
|
-
*
|
|
172
|
-
* Absent `dashboardUrl` IS the unlinked signal — it is only ever set from a repo's cloud.json — so
|
|
173
|
-
* this needs no new plumbing and cannot be wrong about the state it describes.
|
|
174
|
-
*
|
|
175
|
-
* Gated on a VERDICT, not on tool calls. Somebody who has driven the app but proved nothing has not
|
|
176
|
-
* yet received the thing this offers to preserve, and offering to keep nothing is an advert. Past
|
|
177
|
-
* that bar it is a fact about where their record lives, at the foot of a panel they opened on
|
|
178
|
-
* purpose — which is why it does not need to be dismissible.
|
|
179
|
-
*/
|
|
180
|
-
function localOnly(scope, dashboardUrl) {
|
|
110
|
+
function localOnly(scope, dashboardUrl, account) {
|
|
181
111
|
if (dashboardUrl !== undefined)
|
|
182
112
|
return '';
|
|
183
113
|
if (scope.counts.verdicts <= 0)
|
|
184
114
|
return '';
|
|
185
|
-
|
|
115
|
+
if (account === undefined)
|
|
116
|
+
return '';
|
|
117
|
+
const [lead, action, tail] = account.signedIn
|
|
118
|
+
? [REPORT_TEXT.UNLINKED, REPORT_TEXT.UNLINKED_ACTION, REPORT_TEXT.UNLINKED_TAIL]
|
|
119
|
+
: [REPORT_TEXT.LOCAL_ONLY, REPORT_TEXT.LOCAL_ONLY_ACTION, REPORT_TEXT.LOCAL_ONLY_TAIL];
|
|
120
|
+
return `<p class="reticle-report-local-only">${lead} <code>${action}</code> ${tail}</p>`;
|
|
186
121
|
}
|
|
187
|
-
|
|
122
|
+
const SYNC_SPIN_MS = 1_200;
|
|
123
|
+
const SYNCING_ATTR = 'data-syncing';
|
|
188
124
|
export class PresenterReport {
|
|
189
125
|
#panel;
|
|
190
126
|
#body;
|
|
@@ -217,8 +153,6 @@ export class PresenterReport {
|
|
|
217
153
|
});
|
|
218
154
|
root.querySelector(`[${SHARE_IN_ATTR}]`)?.addEventListener('click', (e) => {
|
|
219
155
|
e.stopPropagation();
|
|
220
|
-
// LinkedIn ignores every text parameter, so the caption goes to the clipboard first and the
|
|
221
|
-
// composer opens on the link - the only route that needs no posting permission.
|
|
222
156
|
void this.#copy(this.shareText());
|
|
223
157
|
this.#openShare(buildLinkedInShareUrl());
|
|
224
158
|
});
|
|
@@ -230,8 +164,25 @@ export class PresenterReport {
|
|
|
230
164
|
e.stopPropagation();
|
|
231
165
|
void this.#copy(buildReferralText(), e.currentTarget);
|
|
232
166
|
});
|
|
167
|
+
root.addEventListener('click', (e) => {
|
|
168
|
+
const target = e.target;
|
|
169
|
+
if (!(target instanceof Element))
|
|
170
|
+
return;
|
|
171
|
+
const signin = target.closest(`[${ACCOUNT_SIGNIN_ATTR}]`);
|
|
172
|
+
if (signin !== null) {
|
|
173
|
+
e.stopPropagation();
|
|
174
|
+
void this.#copy(ACCOUNT_TEXT.SIGNIN_COMMAND, signin);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const sync = target.closest(`[${SYNC_BTN_ATTR}]`);
|
|
178
|
+
if (sync !== null) {
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
this.#host.onSyncNow?.();
|
|
181
|
+
sync.setAttribute(SYNCING_ATTR, '1');
|
|
182
|
+
setTimeout(() => sync.removeAttribute(SYNCING_ATTR), SYNC_SPIN_MS);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
233
185
|
}
|
|
234
|
-
/** The record arrived from the daemon. Repaint only if the panel is on screen. */
|
|
235
186
|
setSnapshot(snapshot) {
|
|
236
187
|
this.#snapshot = snapshot;
|
|
237
188
|
if (this.isOpen())
|
|
@@ -264,7 +215,6 @@ export class PresenterReport {
|
|
|
264
215
|
contains(node) {
|
|
265
216
|
return true === this.#panel?.contains(node);
|
|
266
217
|
}
|
|
267
|
-
/** The post text for whichever scope is showing. */
|
|
268
218
|
shareText() {
|
|
269
219
|
const snap = this.#snapshot;
|
|
270
220
|
if (snap === undefined)
|
|
@@ -286,7 +236,7 @@ export class PresenterReport {
|
|
|
286
236
|
this.#body.innerHTML =
|
|
287
237
|
scope === undefined
|
|
288
238
|
? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
|
|
289
|
-
: reportBodyHtml(scope, this.#snapshot?.dashboardUrl);
|
|
239
|
+
: reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName);
|
|
290
240
|
}
|
|
291
241
|
#openShare(url) {
|
|
292
242
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
@@ -296,7 +246,6 @@ export class PresenterReport {
|
|
|
296
246
|
await navigator.clipboard?.writeText(text);
|
|
297
247
|
}
|
|
298
248
|
catch {
|
|
299
|
-
// A blocked clipboard is not worth an error dialog over; the text is on screen either way.
|
|
300
249
|
}
|
|
301
250
|
if (button instanceof HTMLElement) {
|
|
302
251
|
const original = button.textContent ?? '';
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { SessionState } from '@reticlehq/core';
|
|
2
|
-
import { type LogKind, type LogResult } from './presenter-log.js';
|
|
2
|
+
import { type LogKind, type LogResult } from './chrome/presenter-log.js';
|
|
3
3
|
import { type Capabilities } from '../registry/capabilities.js';
|
|
4
|
-
/** The in-page run-state export (Copy/Export buttons). The full event ring-buffer is server-side. */
|
|
5
4
|
export interface PresenterRunState {
|
|
6
5
|
session: string;
|
|
7
6
|
url: string;
|
|
@@ -24,7 +23,6 @@ export interface PresenterRunState {
|
|
|
24
23
|
result?: LogResult;
|
|
25
24
|
}[];
|
|
26
25
|
}
|
|
27
|
-
/** Everything the controller hands the builder - the live fields a run state is computed from. */
|
|
28
26
|
interface RunStateInput {
|
|
29
27
|
sessionId: string;
|
|
30
28
|
state: SessionState;
|
|
@@ -38,10 +36,5 @@ interface RunStateInput {
|
|
|
38
36
|
result?: LogResult;
|
|
39
37
|
}[];
|
|
40
38
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Compute the exported run-state from the controller's live fields: per-kind + pass/fail counts over
|
|
43
|
-
* the activity log, the session duration, and the current capability surface. Extracted from
|
|
44
|
-
* presenter.ts so the controller stays under the size cap; the counting is the same as before.
|
|
45
|
-
*/
|
|
46
39
|
export declare function buildRunState(input: RunStateInput): PresenterRunState;
|
|
47
40
|
export {};
|