@reticlehq/browser 2.14.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/actions/actions-dom.d.ts +9 -0
  2. package/dist/actions/actions-dom.js +81 -28
  3. package/dist/actions/actions-press.d.ts +4 -0
  4. package/dist/actions/actions-press.js +46 -0
  5. package/dist/actions/actions.d.ts +1 -0
  6. package/dist/actions/actions.js +50 -13
  7. package/dist/actions/appeared-text.js +6 -0
  8. package/dist/actions/scroll.d.ts +8 -1
  9. package/dist/actions/scroll.js +28 -10
  10. package/dist/commands/commands.js +10 -3
  11. package/dist/dom/a11y.js +2 -2
  12. package/dist/dom/desktop-capture.js +1 -1
  13. package/dist/dom/dom-ignore.js +1 -1
  14. package/dist/dom/occlusion.js +31 -5
  15. package/dist/dom/paint-context.d.ts +6 -0
  16. package/dist/dom/paint-context.js +38 -0
  17. package/dist/dom/query.js +24 -7
  18. package/dist/dom/snapshot.js +1 -1
  19. package/dist/edit-epoch.d.ts +1 -1
  20. package/dist/edit-epoch.js +1 -1
  21. package/dist/index.d.ts +4 -5
  22. package/dist/index.js +3 -5
  23. package/dist/observers/animation.js +1 -1
  24. package/dist/observers/blind-spots.js +1 -1
  25. package/dist/observers/departure.js +16 -8
  26. package/dist/observers/dialogs.js +1 -1
  27. package/dist/observers/dom.js +28 -8
  28. package/dist/observers/download.js +1 -1
  29. package/dist/observers/health.js +1 -1
  30. package/dist/observers/{network-body.js → net-detail/network-body.js} +2 -2
  31. package/dist/observers/network-stream.js +2 -2
  32. package/dist/observers/network.d.ts +1 -1
  33. package/dist/observers/network.js +51 -10
  34. package/dist/observers/scroll.js +2 -2
  35. package/dist/presenter/chrome/layers.d.ts +2 -0
  36. package/dist/presenter/chrome/layers.js +2 -0
  37. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  38. package/dist/presenter/chrome/log-kinds.js +7 -0
  39. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -1
  40. package/dist/presenter/{presenter-log.d.ts → chrome/presenter-log.d.ts} +3 -12
  41. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +4 -9
  42. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  43. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  44. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -1
  45. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -0
  46. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -0
  47. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -0
  48. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -0
  49. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  50. package/dist/presenter/presenter-account-styles.js +99 -0
  51. package/dist/presenter/presenter-account.d.ts +40 -0
  52. package/dist/presenter/presenter-account.js +178 -0
  53. package/dist/presenter/presenter-config.d.ts +9 -2
  54. package/dist/presenter/presenter-config.js +3 -1
  55. package/dist/presenter/presenter-controls.d.ts +1 -6
  56. package/dist/presenter/presenter-controls.js +7 -2
  57. package/dist/presenter/presenter-dock-layout.js +1 -1
  58. package/dist/presenter/presenter-drag.d.ts +1 -19
  59. package/dist/presenter/presenter-drag.js +3 -50
  60. package/dist/presenter/presenter-glow.js +1 -1
  61. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  62. package/dist/presenter/presenter-hud-position.js +50 -0
  63. package/dist/presenter/presenter-offer.d.ts +25 -0
  64. package/dist/presenter/presenter-offer.js +87 -0
  65. package/dist/presenter/presenter-report-styles.js +20 -1
  66. package/dist/presenter/presenter-report.d.ts +2 -1
  67. package/dist/presenter/presenter-report.js +31 -23
  68. package/dist/presenter/presenter-run-state.d.ts +1 -1
  69. package/dist/presenter/presenter-run-state.js +1 -1
  70. package/dist/presenter/presenter-settings-styles.js +2 -1
  71. package/dist/presenter/presenter-settings.d.ts +8 -0
  72. package/dist/presenter/presenter-settings.js +58 -3
  73. package/dist/presenter/presenter-shell-styles.js +33 -15
  74. package/dist/presenter/presenter-shell.d.ts +8 -0
  75. package/dist/presenter/presenter-shell.js +47 -4
  76. package/dist/presenter/presenter-styles.d.ts +2 -1
  77. package/dist/presenter/presenter-styles.js +8 -3
  78. package/dist/presenter/presenter-workspace.d.ts +4 -0
  79. package/dist/presenter/presenter-workspace.js +29 -2
  80. package/dist/presenter/presenter.d.ts +3 -3
  81. package/dist/presenter/presenter.js +28 -14
  82. package/dist/presenter/tour/tour-view.d.ts +33 -0
  83. package/dist/presenter/tour/tour-view.js +192 -0
  84. package/dist/presenter/tour/tour.d.ts +25 -0
  85. package/dist/presenter/tour/tour.js +264 -0
  86. package/dist/recorder/recorder.js +6 -0
  87. package/dist/registry/declared-channels.d.ts +2 -0
  88. package/dist/registry/declared-channels.js +14 -0
  89. package/dist/registry/declared-platform.d.ts +4 -0
  90. package/dist/registry/declared-platform.js +34 -0
  91. package/dist/registry/emitter.js +1 -1
  92. package/dist/registry/store-adapters.js +1 -1
  93. package/dist/registry/stores.js +1 -1
  94. package/dist/reticle-presenter-helpers.js +10 -6
  95. package/dist/reticle-singleton.d.ts +2 -0
  96. package/dist/reticle-singleton.js +3 -0
  97. package/dist/reticle.js +99 -35
  98. package/dist/review/annotator-styles.js +1 -1
  99. package/dist/review/annotator.js +2 -2
  100. package/dist/review/mark-anchor.js +3 -3
  101. package/dist/transport/transport.js +2 -2
  102. package/package.json +5 -4
  103. package/dist/presenter/presenter-test-helpers.d.ts +0 -14
  104. package/dist/presenter/presenter-test-helpers.js +0 -39
  105. /package/dist/actions/{synthetic-input.d.ts → synthetic/synthetic-input.d.ts} +0 -0
  106. /package/dist/actions/{synthetic-input.js → synthetic/synthetic-input.js} +0 -0
  107. /package/dist/dom/{auto-anchor.d.ts → addressing/auto-anchor.d.ts} +0 -0
  108. /package/dist/dom/{auto-anchor.js → addressing/auto-anchor.js} +0 -0
  109. /package/dist/dom/{refs.d.ts → addressing/refs.d.ts} +0 -0
  110. /package/dist/dom/{refs.js → addressing/refs.js} +0 -0
  111. /package/dist/dom/{source.d.ts → addressing/source.d.ts} +0 -0
  112. /package/dist/dom/{source.js → addressing/source.js} +0 -0
  113. /package/dist/observers/{network-body.d.ts → net-detail/network-body.d.ts} +0 -0
  114. /package/dist/observers/{network-redact.d.ts → net-detail/network-redact.d.ts} +0 -0
  115. /package/dist/observers/{network-redact.js → net-detail/network-redact.js} +0 -0
  116. /package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -0
  117. /package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -0
  118. /package/dist/presenter/{presenter-brand.d.ts → chrome/presenter-brand.d.ts} +0 -0
  119. /package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -0
  120. /package/dist/presenter/{presenter-effects.d.ts → chrome/presenter-effects.d.ts} +0 -0
  121. /package/dist/presenter/{presenter-hud-chrome.d.ts → chrome/presenter-hud-chrome.d.ts} +0 -0
  122. /package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -0
  123. /package/dist/presenter/{presenter-report-copy.d.ts → chrome/presenter-report-copy.d.ts} +0 -0
  124. /package/dist/presenter/{presenter-report-copy.js → chrome/presenter-report-copy.js} +0 -0
  125. /package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -0
  126. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  127. /package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -0
  128. /package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -0
  129. /package/dist/registry/{adapters.js → stores/adapters.js} +0 -0
  130. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
  131. /package/dist/timers/{native-console.js → console/native-console.js} +0 -0
  132. /package/dist/timers/{native-timers.d.ts → native/native-timers.d.ts} +0 -0
  133. /package/dist/timers/{native-timers.js → native/native-timers.js} +0 -0
@@ -1,3 +1,6 @@
1
+ import type { AccountState } from '@reticlehq/core';
2
+ import { type AccountDetails } from './presenter-account.js';
3
+ export declare const TOOLBAR_ACCOUNT_ATTR = "data-reticle-toolbar-account";
1
4
  export declare function readWorkspaceRoot(): string | undefined;
2
5
  export declare function workspaceFolderLabel(root: string): string;
3
6
  export declare function readProjectIdFromUrl(): string | undefined;
@@ -9,3 +12,4 @@ export declare function workspaceSummary(): {
9
12
  export declare function workspaceRowHtml(): string;
10
13
  export declare function paintWorkspace(root: HTMLElement): void;
11
14
  export declare function mountWorkspaceSelector(root: HTMLElement): () => void;
15
+ export declare function paintToolbarAccount(root: HTMLElement, account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
@@ -1,11 +1,14 @@
1
1
  import { RETICLE_ROOT_GLOBAL, RETICLE_URL_PARAM } from '@reticlehq/core';
2
- import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './presenter-icons.js';
2
+ import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './icons/presenter-icons.js';
3
+ import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, accountControlHtml, } from './presenter-account.js';
3
4
  const WORKSPACE_BTN_ATTR = 'data-reticle-workspace-btn';
4
5
  const WORKSPACE_MENU_ATTR = 'data-reticle-workspace-menu';
5
6
  const WORKSPACE_NAME_ATTR = 'data-reticle-workspace-name';
6
7
  const WORKSPACE_PATH_ATTR = 'data-reticle-workspace-path';
7
8
  const WORKSPACE_PROJECT_ATTR = 'data-reticle-workspace-project';
8
9
  const WORKSPACE_COPY_ATTR = 'data-reticle-workspace-copy';
10
+ export const TOOLBAR_ACCOUNT_ATTR = 'data-reticle-toolbar-account';
11
+ const COPIED_MS = 1_200;
9
12
  const WORKSPACE_LABEL = 'Workspace';
10
13
  const PROJECT_LABEL = 'Project';
11
14
  const COPY_PATH_LABEL = 'Copy path';
@@ -53,7 +56,9 @@ export function workspaceRowHtml() {
53
56
  <div class="reticle-workspace-menu" ${WORKSPACE_MENU_ATTR} role="region" aria-label="${WORKSPACE_LABEL}" aria-hidden="true" hidden>
54
57
  <div class="reticle-workspace-menu-head">
55
58
  <div class="reticle-workspace-menu-title">${WORKSPACE_LABEL}</div>
56
- <button type="button" class="reticle-workspace-copy" ${WORKSPACE_COPY_ATTR} title="${COPY_PATH_LABEL}" aria-label="${COPY_PATH_LABEL}">${copyIcon}</button>
59
+ <div class="reticle-workspace-menu-actions">
60
+ <button type="button" class="reticle-workspace-copy" ${WORKSPACE_COPY_ATTR} title="${COPY_PATH_LABEL}" aria-label="${COPY_PATH_LABEL}">${copyIcon}</button>
61
+ </div>
57
62
  </div>
58
63
  <div class="reticle-workspace-menu-row"><span class="reticle-workspace-menu-k">Folder</span><span class="reticle-workspace-menu-v" data-reticle-workspace-folder></span></div>
59
64
  <div class="reticle-workspace-menu-row"><span class="reticle-workspace-menu-k">Path</span><span class="reticle-workspace-menu-v reticle-workspace-menu-path" ${WORKSPACE_PATH_ATTR}></span></div>
@@ -128,6 +133,22 @@ export function mountWorkspaceSelector(root) {
128
133
  e.stopPropagation();
129
134
  toggle();
130
135
  };
136
+ const onMenuClick = (e) => {
137
+ const target = e.target;
138
+ if (!(target instanceof Element))
139
+ return;
140
+ const signin = target.closest(`[${ACCOUNT_SIGNIN_ATTR}]`);
141
+ if (null === signin)
142
+ return;
143
+ e.stopPropagation();
144
+ void navigator.clipboard?.writeText(ACCOUNT_TEXT.SIGNIN_COMMAND).catch(() => undefined);
145
+ const previous = signin.textContent;
146
+ signin.textContent = ACCOUNT_TEXT.COPIED;
147
+ setTimeout(() => {
148
+ signin.textContent = previous;
149
+ }, COPIED_MS);
150
+ };
151
+ menu.addEventListener('click', onMenuClick);
131
152
  const onDocPointer = (e) => {
132
153
  const target = e.target;
133
154
  if (!(target instanceof Node))
@@ -171,3 +192,9 @@ export function mountWorkspaceSelector(root) {
171
192
  document.removeEventListener('keydown', onKeyDown);
172
193
  };
173
194
  }
195
+ export function paintToolbarAccount(root, account, dashboardUrl, details = {}) {
196
+ const slot = root.querySelector(`[${TOOLBAR_ACCOUNT_ATTR}]`);
197
+ if (!(slot instanceof HTMLElement))
198
+ return;
199
+ slot.innerHTML = accountControlHtml(account, { ...details, dashboardUrl }, true);
200
+ }
@@ -1,13 +1,13 @@
1
1
  import { PresenterMode, SessionState, type PresenterTone } from '@reticlehq/core';
2
- import { type LogKind, type LogResult, type LogHandle } from './presenter-log.js';
2
+ import { type LogKind, type LogResult, type LogHandle } from './chrome/presenter-log.js';
3
3
  import { type PresenterOptions } from './presenter-config.js';
4
4
  import { type PresenterRunState } from './presenter-run-state.js';
5
5
  import { type ControlHandler } from './presenter-controls.js';
6
6
  import { Annotator } from '../review/annotator.js';
7
7
  export { GlowPhase } from './presenter-config.js';
8
8
  export type { PresenterOptions } from './presenter-config.js';
9
- export { LOG_KIND, LOG_RESULT } from './presenter-log.js';
10
- export type { LogHandle } from './presenter-log.js';
9
+ export { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
10
+ export type { LogHandle } from './chrome/presenter-log.js';
11
11
  export type { ControlIntent, ControlHandler } from './presenter-controls.js';
12
12
  export declare class Presenter {
13
13
  #private;
@@ -1,23 +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
19
  export { GlowPhase } from './presenter-config.js';
20
- export { LOG_KIND, LOG_RESULT } from './presenter-log.js';
20
+ export { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
21
21
  export class Presenter {
22
22
  #paceMs;
23
23
  #root;
@@ -85,6 +85,7 @@ export class Presenter {
85
85
  onExpand: () => this.#syncAnnotator(),
86
86
  onAnnotateToggle: () => this.#syncAnnotator(),
87
87
  onCollapse: () => this.#syncAnnotator(),
88
+ onSyncNow: () => this.#onControl?.({ kind: HumanControlKind.SYNC }),
88
89
  settings: {
89
90
  onBeforeOpen: () => {
90
91
  if (this.#shell.isCollapsed())
@@ -92,6 +93,7 @@ export class Presenter {
92
93
  },
93
94
  onHideUntilRestart: () => this.#applyHideUntilRestart(),
94
95
  onSettingsChange: (s) => this.#onSettingsChange(s),
96
+ onHarness: (enabled) => this.#onControl?.({ kind: HumanControlKind.HARNESS, text: enabled ? 'on' : 'off' }),
95
97
  },
96
98
  });
97
99
  }
@@ -116,8 +118,17 @@ export class Presenter {
116
118
  return void this.#panel.setFlows(a['flows']);
117
119
  if (command.name === ReticleCommand.IMPACT) {
118
120
  const snapshot = parseImpactSnapshot(a['snapshot']);
119
- if (snapshot !== undefined)
121
+ if (snapshot !== undefined) {
120
122
  this.#shell.report.setSnapshot(snapshot);
123
+ this.#shell.paintOffer(snapshot.harnessOffer);
124
+ this.#shell.paintHarness(snapshot.harnessConfig);
125
+ this.#shell.paintAccount(snapshot.account, snapshot.dashboardUrl, {
126
+ projectName: snapshot.projectName,
127
+ dashboardUrl: snapshot.dashboardUrl,
128
+ verdicts: snapshot.project.counts.verdicts,
129
+ defects: snapshot.project.counts.failed,
130
+ });
131
+ }
121
132
  return;
122
133
  }
123
134
  const state = a['state'];
@@ -352,14 +363,17 @@ export class Presenter {
352
363
  }
353
364
  if (idleMs < this.#idleNoticeMs)
354
365
  return;
355
- const since = this.#lastActionText !== '' ? ACT_STRIP.SINCE_LAST : '';
356
- this.#paintActStrip(`${ACT_STRIP.IDLE_PREFIX}${humanDuration(idleMs)}${since}`, true);
366
+ if ('' === this.#lastActionText)
367
+ return;
368
+ const since = ACT_STRIP.SINCE_LAST;
369
+ this.#paintActStrip(`${ACT_STRIP.PLANNING_PREFIX}${humanDuration(idleMs)}${since}`, true);
357
370
  if (this.#chatPillTime !== undefined)
358
371
  this.#chatPillTime.textContent = humanDuration(idleMs);
359
372
  }
360
373
  #endIdle(idleMs) {
361
374
  this.#endMs = this.#now();
362
- this.#panel.setState(SessionState.ENDED, `idle ${humanDuration(idleMs)}`);
375
+ this.#panel.setState(SessionState.ENDED, `${ACT_STRIP.STOPPED_PREFIX}${humanDuration(idleMs)} quiet`);
376
+ this.#paintActStrip(ACT_STRIP.STOPPED, false);
363
377
  if (this.#heartbeatTimer !== undefined) {
364
378
  nativeClearTimeout(this.#heartbeatTimer);
365
379
  this.#heartbeatTimer = undefined;
@@ -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,192 @@
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('&amp;')
37
+ .split('<')
38
+ .join('&lt;')
39
+ .split('>')
40
+ .join('&gt;')
41
+ .split('"')
42
+ .join('&quot;');
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%).
115
+ It is cleared for the clicks too, and that half was missing. A scrim blocks only while it is
116
+ VISIBLY blocking: reported from the field on 3.1.0 by several users, one of whom removed the
117
+ plugin, because a cleared scrim went on eating every press on a page that looked lit and
118
+ reachable. An overlay that looks transparent and behaves opaque reads as the app being broken. */
119
+ [${TOUR_ATTR}] .reticle-tour-scrim.is-clear{background:transparent;backdrop-filter:none;
120
+ pointer-events:none;}
121
+ /* Above the ring, and the z-index is the whole point.
122
+ The ring is appended after the card, and both are positioned with an auto z-index, so the ring
123
+ painted on top of it -- and the ring's dimming IS a 9999px shadow, which therefore fell across the
124
+ card as well. On every ringed slide the card was washed at 55% and read as sitting BEHIND the page
125
+ it was drawn over. The ring must stay above the scrim and below the card; saying so explicitly is
126
+ the fix, because DOM order alone cannot express "between these two". */
127
+ [${TOUR_ATTR}] .reticle-tour-card{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
128
+ width:min(430px,calc(100vw - 32px));box-sizing:border-box;pointer-events:auto;z-index:3;
129
+ background:#14161d;color:#e8eaf0;border:1px solid #2b303d;border-radius:14px;padding:20px 20px 16px;
130
+ box-shadow:0 24px 60px rgba(0,0,0,.5);}
131
+ [${TOUR_ATTR}] .reticle-tour-skip{position:absolute;top:10px;right:12px;background:none;border:0;
132
+ color:#8a93a6;font:inherit;font-size:12px;cursor:pointer;padding:4px 6px;border-radius:6px;}
133
+ [${TOUR_ATTR}] .reticle-tour-skip:hover{color:#e8eaf0;background:rgba(255,255,255,.06);}
134
+ [${TOUR_ATTR}] .reticle-tour-step{margin:0 0 6px;font-size:11px;letter-spacing:.06em;
135
+ text-transform:uppercase;color:#7f8798;}
136
+ [${TOUR_ATTR}] .reticle-tour-title{margin:0 0 8px;font-size:18px;line-height:1.25;font-weight:650;
137
+ color:#f2f4f8;}
138
+ [${TOUR_ATTR}] .reticle-tour-body{margin:0 0 10px;color:#c9cedb;}
139
+ /* A call named inside a sentence. Sets its own colour for the reason every other text class does:
140
+ an inherited one loses to any direct element selector in the host app. */
141
+ [${TOUR_ATTR}] .reticle-tour-code{padding:1px 5px;border-radius:5px;background:#0c0e14;
142
+ border:1px solid #232834;color:#9fb6ff;
143
+ font:11.5px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;overflow-wrap:anywhere;}
144
+ [${TOUR_ATTR}] .reticle-tour-try{margin:0 0 12px;padding:8px 10px;border-radius:8px;
145
+ background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.32);color:#c7cbff;
146
+ font-size:12.5px;font-weight:550;}
147
+ [${TOUR_ATTR}] .reticle-tour-prompts{display:flex;flex-direction:column;gap:8px;margin:0 0 12px;}
148
+ [${TOUR_ATTR}] .reticle-tour-prompt{display:grid;gap:6px;padding:9px 10px;border-radius:8px;
149
+ background:#0c0e14;border:1px solid #232834;
150
+ grid-template-columns:1fr auto;grid-template-areas:"label copy" "text text";align-items:center;}
151
+ [${TOUR_ATTR}] .reticle-tour-prompt-label{grid-area:label;color:#8a93a6;font-size:11px;
152
+ letter-spacing:.05em;text-transform:uppercase;}
153
+ [${TOUR_ATTR}] .reticle-tour-prompt-text{grid-area:text;width:100%;box-sizing:border-box;margin:0;
154
+ white-space:pre-wrap;overflow-wrap:anywhere;color:#c9cedb;
155
+ font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;}
156
+ [${TOUR_ATTR}] .reticle-tour-copy{grid-area:copy;padding:5px 10px;border-radius:6px;
157
+ border:1px solid #2b303d;background:transparent;color:#c9cedb;font:inherit;font-size:11.5px;
158
+ font-weight:600;cursor:pointer;white-space:nowrap;}
159
+ [${TOUR_ATTR}] .reticle-tour-copy:hover{background:rgba(255,255,255,.07);color:#fff;}
160
+ /* The flash that tells somebody the click did something. Colour AND text: colour alone is not an
161
+ answer for the people who cannot see the difference. */
162
+ [${TOUR_ATTR}] .reticle-tour-copy.is-done{background:#16a34a;border-color:#16a34a;color:#fff;}
163
+ [${TOUR_ATTR}] .reticle-tour-copy.is-manual{background:#b45309;border-color:#b45309;color:#fff;}
164
+ [${TOUR_ATTR}] .reticle-tour-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;}
165
+ [${TOUR_ATTR}] .reticle-tour-dots{display:flex;gap:6px;}
166
+ [${TOUR_ATTR}] .reticle-tour-dot{width:6px;height:6px;border-radius:50%;background:#333a49;}
167
+ [${TOUR_ATTR}] .reticle-tour-dot.is-on{background:#6366f1;}
168
+ [${TOUR_ATTR}] .reticle-tour-nav{display:flex;gap:8px;}
169
+ [${TOUR_ATTR}] .reticle-tour-back,[${TOUR_ATTR}] .reticle-tour-next{padding:8px 14px;border-radius:8px;
170
+ font:inherit;font-weight:600;cursor:pointer;border:1px solid #2b303d;background:transparent;color:#c9cedb;}
171
+ [${TOUR_ATTR}] .reticle-tour-next{background:#6366f1;border-color:#6366f1;color:#fff;}
172
+ [${TOUR_ATTR}] .reticle-tour-next:hover{filter:brightness(1.08);}
173
+ [${TOUR_ATTR}] .reticle-tour-back:hover{background:rgba(255,255,255,.06);color:#e8eaf0;}
174
+ [${TOUR_ATTR}] .reticle-tour-ring{position:absolute;border-radius:14px;pointer-events:none;z-index:1;
175
+ box-shadow:0 0 0 3px #6366f1,0 0 0 9999px rgba(6,8,14,.55);transition:all .25s ease;}
176
+ /* A ring somebody is being asked to press, rather than to look at: it pulses, so the invitation in
177
+ the prose has something on the page agreeing with it. */
178
+ [${TOUR_ATTR}] .reticle-tour-ring.is-live{animation:reticle-tour-pulse 1.8s ease-in-out infinite;}
179
+ @keyframes reticle-tour-pulse{
180
+ 0%,100%{box-shadow:0 0 0 3px #6366f1,0 0 0 9999px rgba(6,8,14,.55);}
181
+ 50%{box-shadow:0 0 0 6px rgba(99,102,241,.55),0 0 0 9999px rgba(6,8,14,.55);}
182
+ }
183
+ /* A REGION, not a target. The spotlight above cuts a hole in the dimming, which is right for
184
+ something small and wrong for the app: the hole becomes the whole viewport, the dimming
185
+ disappears, and the card is left competing with a fully lit page while the ring edges sit off at
186
+ the margins pointing at nothing. An outline says "all of this" without turning the lights on. */
187
+ [${TOUR_ATTR}] .reticle-tour-ring.is-region{box-shadow:none;border:2px dashed #7c83f5;
188
+ background:rgba(99,102,241,.06);}
189
+ @media (prefers-reduced-motion:reduce){
190
+ [${TOUR_ATTR}] .reticle-tour-ring{transition:none;}
191
+ }
192
+ `;
@@ -0,0 +1,25 @@
1
+ import { isLastSlide } from './tour-view.js';
2
+ export declare const TOUR_SEEN_KEY_PREFIX = "reticle.tour.seen.";
3
+ export declare const tourSeenKey: (projectId: string) => string;
4
+ export interface TourStorage {
5
+ getItem: (key: string) => string | null;
6
+ setItem: (key: string, value: string) => void;
7
+ }
8
+ export interface TourDeps {
9
+ readonly document: Document;
10
+ readonly storage: TourStorage | undefined;
11
+ readonly projectId: string;
12
+ readonly isDriving: () => boolean;
13
+ readonly search?: string;
14
+ readonly copy?: (text: string) => Promise<boolean> | boolean;
15
+ readonly select?: (element: Element) => void;
16
+ }
17
+ export interface TourHandle {
18
+ readonly destroy: () => void;
19
+ readonly isOpen: () => boolean;
20
+ }
21
+ export declare function openedByReticle(search: string | undefined): boolean;
22
+ export declare function tourAlreadySeen(storage: TourStorage | undefined, projectId: string): boolean;
23
+ export declare function mountTour(deps: TourDeps): TourHandle | undefined;
24
+ export { isLastSlide };
25
+ export declare function safeLocalStorage(): TourStorage | undefined;