@docentjs/core 0.4.0 → 0.5.1

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/index.cjs CHANGED
@@ -442,6 +442,28 @@ var TourController = class {
442
442
  });
443
443
  await this.finish();
444
444
  }
445
+ /**
446
+ * Swap in a new definition of this tour (live editing). A running tour
447
+ * re-renders its current step, or the nearest one if that step was removed.
448
+ */
449
+ async updateTour(tour) {
450
+ const currentId = this.currentStep()?.id;
451
+ this.tour = tour;
452
+ if (!this.isActive()) return;
453
+ let index = currentId === void 0 ? -1 : tour.steps.findIndex((s) => s.id === currentId);
454
+ if (index === -1) index = Math.min(this.state.index, tour.steps.length - 1);
455
+ if (index < 0) {
456
+ await this.abort("tour-emptied");
457
+ return;
458
+ }
459
+ const history = this.state.history.filter((i) => i < tour.steps.length && i !== index);
460
+ this.setState({
461
+ ...this.state,
462
+ index,
463
+ history
464
+ });
465
+ if (this.state.status === "running") await this.showCurrent();
466
+ }
445
467
  /** Report a named application event. Advances a step waiting on it. */
446
468
  notify(eventName) {
447
469
  const advance = this.currentStep()?.advance;
@@ -835,6 +857,17 @@ var Docent = class {
835
857
  if (tour && record.version < tourVersion(tour)) return "not-started";
836
858
  return record.state;
837
859
  }
860
+ /**
861
+ * Replace a tour's definition, e.g. from a live editor. If it is running, the
862
+ * current step re-renders with the new content. Triggers are not re-armed, so
863
+ * editing never starts a tour by itself.
864
+ */
865
+ async updateTour(tour) {
866
+ await this.ready;
867
+ this.tours.set(tour.id, tour);
868
+ if (this.activeId === tour.id) await this.controller?.updateTour(tour);
869
+ this.emitState();
870
+ }
838
871
  /** Forget progress for one tour, or all of them, so they show again. */
839
872
  async reset(tourId) {
840
873
  await this.ready;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/schema/tour.ts","../src/define.ts","../src/engine/route.ts","../src/engine/conditions.ts","../src/seams.ts","../src/engine/progress.ts","../src/engine/events.ts","../src/engine/reducer.ts","../src/engine/controller.ts","../src/manager/scoped-storage.ts","../src/manager/docent.ts"],"sourcesContent":["/**\n * Tour schema — the JSON contract shared by the core engine, every renderer,\n * the visual builder and the hosted service.\n *\n * Everything in this file must stay JSON-serialisable. Functions (hooks) live\n * in `hooks.ts` and are attached at runtime, keyed by step id.\n */\n\n/** Current schema version. Bump only on breaking changes to this file. */\nexport const SCHEMA_VERSION = 1 as const\n\nexport type SchemaVersion = typeof SCHEMA_VERSION\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\n/**\n * Where a step points.\n *\n * - A bare string is a CSS selector (web only). Convenient for hand-written tours.\n * - A {@link TargetSpec} is the portable, self-healing form the builder produces.\n *\n * Prefer `{ name }` over raw selectors: on the web it resolves to\n * `[data-docent=\"<name>\"]`, on native to a `testID`, so one tour works everywhere.\n */\nexport type Target = string | TargetSpec\n\nexport interface TargetSpec {\n /**\n * Logical name. Web: `[data-docent=\"<name>\"]`. Native: `testID` / `nativeID`.\n * The most robust anchor; survives refactors and works cross-platform.\n */\n name?: string\n /**\n * Ordered CSS selector fallbacks (web only). Tried in order until one matches.\n * The builder fills several so a tour keeps working after markup changes.\n */\n selectors?: string[]\n /** Explicit native identifier when it differs from `name`. */\n native?: string\n /** Restrict the search to a container matched by this selector. */\n within?: string\n /** When a selector matches several elements, pick this index (default 0). */\n nth?: number\n}\n\n// ---------------------------------------------------------------------------\n// Placement & visuals\n// ---------------------------------------------------------------------------\n\nexport type Side = 'top' | 'right' | 'bottom' | 'left'\nexport type Alignment = 'start' | 'center' | 'end'\n\n/**\n * Preferred popover position relative to the target. `auto` lets the renderer\n * pick the side with the most room. Any placement flips or shifts when it\n * would overflow the viewport.\n */\nexport type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`\n\nexport interface SpotlightOptions {\n /** Space between the target's edge and the cutout, in px. */\n padding?: number\n /** Corner radius of the cutout, in px. */\n radius?: number\n /** Animate the cutout moving between targets. */\n animate?: boolean\n}\n\nexport interface OverlayOptions {\n /** Backdrop colour, any CSS colour. */\n color?: string\n /** Backdrop opacity, 0–1. */\n opacity?: number\n}\n\nexport interface ScrollOptions {\n /** Scroll the target into view before showing the step. */\n enabled?: boolean\n behavior?: 'auto' | 'smooth'\n block?: 'start' | 'center' | 'end' | 'nearest'\n}\n\nexport interface Media {\n type: 'image' | 'video'\n src: string\n alt?: string\n}\n\n// ---------------------------------------------------------------------------\n// Behaviour\n// ---------------------------------------------------------------------------\n\n/**\n * How a step completes.\n *\n * - `'button'` (default): the user presses Next.\n * - `click` / `input`: the user interacts with the target (or another element).\n * - `event`: a named event is emitted through the runtime.\n * - `element`: an element appears (e.g. a menu the user was asked to open).\n * - `delay`: automatically after `ms`.\n */\nexport type Advance =\n | 'button'\n | { on: 'click'; target?: Target }\n | { on: 'input'; target?: Target; match?: string }\n | { on: 'event'; name: string }\n | { on: 'element'; target: Target }\n | { on: 'delay'; ms: number }\n\n/** Whether the user can interact with the spotlighted target. */\nexport type Interaction = 'block' | 'allow'\n\n/** What to do when a step's target cannot be found. */\nexport type OnMissing = 'skip' | 'wait' | 'abort'\n\nexport interface StepButtons {\n back?: boolean\n next?: boolean\n skip?: boolean\n close?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Conditions & triggers\n// ---------------------------------------------------------------------------\n\nexport type TraitValue = string | number | boolean | null | string[]\n\nexport type TraitOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'in'\n | 'nin'\n | 'contains'\n | 'exists'\n | 'missing'\n\n/**\n * Serialisable predicate evaluated by the core at runtime.\n * `custom` predicates are registered by name on the runtime.\n */\nexport type Condition =\n | { type: 'trait'; key: string; op: TraitOperator; value?: TraitValue }\n | { type: 'route'; pattern: string }\n | { type: 'element'; target: Target; exists?: boolean }\n | { type: 'tour'; id: string; state: TourProgressState }\n | { type: 'all'; conditions: Condition[] }\n | { type: 'any'; conditions: Condition[] }\n | { type: 'not'; condition: Condition }\n | { type: 'custom'; name: string; args?: Record<string, TraitValue> }\n\nexport type TourProgressState = 'not-started' | 'in-progress' | 'completed' | 'skipped'\n\n/** What starts a tour. `manual` means only through the runtime API. */\nexport type Trigger =\n | { type: 'manual' }\n | { type: 'auto'; delay?: number }\n | { type: 'route'; pattern: string; delay?: number }\n | { type: 'element'; target: Target; delay?: number }\n | { type: 'event'; name: string }\n\n/**\n * How often an eligible user sees the tour.\n * - `once`: show once per {@link Tour.version}, however it ended.\n * - `until-completed`: keep offering it until the user finishes it.\n * - `always`: every time the trigger fires.\n */\nexport type Frequency = 'once' | 'until-completed' | 'always'\n\n// ---------------------------------------------------------------------------\n// Theme\n// ---------------------------------------------------------------------------\n\n/**\n * Visual tokens, serialisable so a builder or customizer can produce them.\n * Each maps to a CSS custom property in the renderer (`--docent-*`).\n * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.\n */\nexport interface Theme {\n background?: string\n foreground?: string\n muted?: string\n accent?: string\n accentForeground?: string\n radius?: string\n shadow?: string\n font?: string\n width?: string\n overlay?: string\n overlayOpacity?: string\n duration?: string\n zIndex?: string\n}\n\n// ---------------------------------------------------------------------------\n// Labels\n// ---------------------------------------------------------------------------\n\nexport interface Labels {\n next?: string\n back?: string\n skip?: string\n done?: string\n close?: string\n /** Supports `{current}` and `{total}` placeholders. */\n progress?: string\n}\n\n// ---------------------------------------------------------------------------\n// Step\n// ---------------------------------------------------------------------------\n\nexport interface Step {\n /** Unique within the tour. Used for persistence, hooks and analytics. */\n id: string\n /** Omit for a centred modal step (welcome / finish screens). */\n target?: Target\n title?: string\n body?: string\n /** How `body` is interpreted. Renderers never inject raw HTML. */\n format?: 'text' | 'markdown'\n media?: Media\n placement?: Placement\n /** Per-step override of the tour's spotlight options. */\n spotlight?: SpotlightOptions\n advance?: Advance\n interaction?: Interaction\n /** Skip this step when the condition is false. */\n condition?: Condition\n onMissing?: OnMissing\n /** How long to wait for the target when `onMissing` is `wait`, in ms. */\n waitFor?: number\n /** URL pattern this step belongs to. Enables multi-page tours. */\n route?: string\n buttons?: StepButtons\n scroll?: ScrollOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Tour\n// ---------------------------------------------------------------------------\n\nexport interface TourOptions {\n /** Persist progress so the tour survives navigation and reloads. */\n persist?: boolean\n frequency?: Frequency\n showProgress?: boolean\n /** Allow closing with Escape or the close button. */\n allowClose?: boolean\n closeOnOverlayClick?: boolean\n keyboard?: boolean\n spotlight?: SpotlightOptions\n overlay?: OverlayOptions\n scroll?: ScrollOptions\n labels?: Labels\n /** Visual tokens applied on top of the renderer's theme. */\n theme?: Theme\n /** Name of a template registered on the renderer (slots, css, theme). */\n template?: string\n}\n\nexport interface Tour {\n schemaVersion: SchemaVersion\n /** Stable identifier. Used for persistence, targeting and analytics. */\n id: string\n /** Bump to re-show the tour to users who already saw an older version. */\n version?: number\n /** Human-readable name, mainly for the builder and dashboards. */\n name?: string\n description?: string\n steps: Step[]\n trigger?: Trigger\n /** All must hold for the tour to be eligible. */\n conditions?: Condition[]\n options?: TourOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n","import { SCHEMA_VERSION, type SchemaVersion, type Tour } from './schema/tour'\n\n/**\n * Identity helper that gives hand-written tours full type inference and\n * fills in the schema version. Returns the same object.\n */\nexport function defineTour(\n tour: Omit<Tour, 'schemaVersion'> & { schemaVersion?: SchemaVersion },\n): Tour {\n return { ...tour, schemaVersion: SCHEMA_VERSION }\n}\n","/**\n * Route pattern matching for `route` triggers, conditions and step routes.\n *\n * Patterns are path globs:\n * - `/settings` exact\n * - `/users/:id` one segment (named for readability, value ignored)\n * - `/users/*` one segment\n * - `/docs/**` zero or more segments\n *\n * Query strings and hashes are ignored. Trailing slashes are tolerated.\n */\n\nfunction segments(path: string): string[] {\n const clean = path.split(/[?#]/, 1)[0] ?? ''\n return clean.split('/').filter(Boolean)\n}\n\nfunction matchSegments(pattern: string[], path: string[], pi = 0, si = 0): boolean {\n if (pi === pattern.length) return si === path.length\n const p = pattern[pi]\n if (p === '**') {\n for (let k = si; k <= path.length; k++) {\n if (matchSegments(pattern, path, pi + 1, k)) return true\n }\n return false\n }\n if (si === path.length) return false\n if (p === '*' || p?.startsWith(':') || p === path[si]) {\n return matchSegments(pattern, path, pi + 1, si + 1)\n }\n return false\n}\n\nexport function matchRoute(pattern: string, path: string): boolean {\n return matchSegments(segments(pattern), segments(path))\n}\n","/**\n * Evaluates serialisable {@link Condition}s against a runtime environment.\n * Pure: everything it needs is passed in.\n */\n\nimport type { Condition, Target, TourProgressState, TraitValue } from '../schema/tour'\nimport type { Identity } from '../seams'\nimport { matchRoute } from './route'\n\nexport type CustomPredicate = (args?: Record<string, TraitValue>) => boolean\n\nexport interface ConditionEnv {\n identity: Identity\n /** Current path, e.g. `/invoices/new`. */\n route?: string\n elementExists?: (target: Target) => boolean\n tourState?: (tourId: string) => TourProgressState\n custom?: Record<string, CustomPredicate>\n}\n\nfunction sameValue(a: TraitValue | undefined, b: TraitValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => v === b[i])\n }\n return a === b\n}\n\nfunction compare(a: TraitValue | undefined, b: TraitValue | undefined): number | null {\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n return null\n}\n\nfunction contains(haystack: TraitValue | undefined, needle: TraitValue | undefined): boolean {\n if (Array.isArray(haystack)) return typeof needle === 'string' && haystack.includes(needle)\n if (typeof haystack === 'string') {\n return (\n (typeof needle === 'string' || typeof needle === 'number') &&\n haystack.includes(String(needle))\n )\n }\n return false\n}\n\nfunction inList(value: TraitValue | undefined, list: TraitValue | undefined): boolean {\n if (!Array.isArray(list)) return false\n if (Array.isArray(value)) return value.some((v) => list.includes(v))\n return typeof value === 'string' && list.includes(value)\n}\n\nexport function evaluateTrait(\n actual: TraitValue | undefined,\n op: Extract<Condition, { type: 'trait' }>['op'],\n expected: TraitValue | undefined,\n): boolean {\n switch (op) {\n case 'exists':\n return actual !== undefined && actual !== null\n case 'missing':\n return actual === undefined || actual === null\n case 'eq':\n return sameValue(actual, expected)\n case 'neq':\n return !sameValue(actual, expected)\n case 'in':\n return inList(actual, expected)\n case 'nin':\n return !inList(actual, expected)\n case 'contains':\n return contains(actual, expected)\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const c = compare(actual, expected)\n if (c === null) return false\n if (op === 'gt') return c > 0\n if (op === 'gte') return c >= 0\n if (op === 'lt') return c < 0\n return c <= 0\n }\n }\n}\n\nexport function evaluateCondition(condition: Condition, env: ConditionEnv): boolean {\n switch (condition.type) {\n case 'trait':\n return evaluateTrait(env.identity.traits[condition.key], condition.op, condition.value)\n case 'route':\n return env.route !== undefined && matchRoute(condition.pattern, env.route)\n case 'element': {\n const exists = env.elementExists?.(condition.target) ?? false\n return exists === (condition.exists ?? true)\n }\n case 'tour':\n return (env.tourState?.(condition.id) ?? 'not-started') === condition.state\n case 'all':\n return condition.conditions.every((c) => evaluateCondition(c, env))\n case 'any':\n return condition.conditions.some((c) => evaluateCondition(c, env))\n case 'not':\n return !evaluateCondition(condition.condition, env)\n case 'custom':\n return env.custom?.[condition.name]?.(condition.args) ?? false\n }\n}\n\n/** All conditions must hold. An empty or missing list holds. */\nexport function evaluateAll(conditions: Condition[] | undefined, env: ConditionEnv): boolean {\n return (conditions ?? []).every((c) => evaluateCondition(c, env))\n}\n","/**\n * Extension seams — the interfaces the runtime is built against so that tours,\n * user identity, storage and analytics can come from anywhere: inline JSON,\n * a local file, the user's own backend, or a hosted service.\n */\n\nimport type { Tour, TraitValue } from './schema/tour'\n\nexport type MaybePromise<T> = T | Promise<T>\n\n// ---------------------------------------------------------------------------\n// Tour source\n// ---------------------------------------------------------------------------\n\nexport interface TourSource {\n /** Return every tour this source knows about. */\n load(): MaybePromise<Tour[]>\n /**\n * Optional live updates. Return an unsubscribe function.\n * Lets a hosted source push tour changes without a page reload.\n */\n subscribe?(listener: (tours: Tour[]) => void): () => void\n}\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\nexport interface Identity {\n /** Stable user id. Omit for anonymous visitors. */\n id?: string\n /** Attributes that `trait` conditions evaluate against. */\n traits: Record<string, TraitValue>\n}\n\nexport const ANONYMOUS_IDENTITY: Identity = { traits: {} }\n\n// ---------------------------------------------------------------------------\n// Storage\n// ---------------------------------------------------------------------------\n\n/**\n * Key/value storage for progress and seen-state. Shape matches `localStorage`\n * but every method may be async so React Native's AsyncStorage fits too.\n */\nexport interface StorageAdapter {\n get(key: string): MaybePromise<string | null>\n set(key: string, value: string): MaybePromise<void>\n remove(key: string): MaybePromise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DocentEventType =\n | 'tour:started'\n | 'tour:completed'\n | 'tour:skipped'\n | 'tour:aborted'\n | 'step:shown'\n | 'step:completed'\n | 'step:skipped'\n | 'step:missing'\n\nexport interface DocentEvent {\n type: DocentEventType\n tourId: string\n tourVersion: number\n stepId?: string\n stepIndex?: number\n /** Unix epoch milliseconds. */\n timestamp: number\n identity: Identity\n}\n\n/** Receives every lifecycle event. Wire it to console, your analytics, or a hosted endpoint. */\nexport interface EventSink {\n emit(event: DocentEvent): void\n}\n","/**\n * Persisted per-tour progress: what the user has seen, where they stopped,\n * and whether the tour should be offered again.\n */\n\nimport type { Frequency, Tour, TourProgressState } from '../schema/tour'\nimport type { StorageAdapter } from '../seams'\n\nexport interface TourRecord {\n tourId: string\n version: number\n state: TourProgressState\n /** Step to resume from when the tour was interrupted. */\n stepId?: string\n /** Unix epoch milliseconds. */\n updatedAt: number\n}\n\nexport const STORAGE_PREFIX = 'docent:'\n\nexport function storageKey(tourId: string): string {\n return `${STORAGE_PREFIX}${tourId}`\n}\n\nexport function tourVersion(tour: Tour): number {\n return tour.version ?? 1\n}\n\n/** Whether a tour should be offered given what the user has already done. */\nexport function shouldShow(tour: Tour, record: TourRecord | null): boolean {\n if (!record) return true\n if (record.version < tourVersion(tour)) return true\n const frequency: Frequency = tour.options?.frequency ?? 'once'\n switch (frequency) {\n case 'always':\n return true\n case 'until-completed':\n return record.state !== 'completed'\n case 'once':\n return record.state === 'not-started' || record.state === 'in-progress'\n }\n}\n\nexport class ProgressStore {\n constructor(private readonly storage: StorageAdapter) {}\n\n async get(tourId: string): Promise<TourRecord | null> {\n const raw = await this.storage.get(storageKey(tourId))\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<TourRecord>\n if (parsed.tourId !== tourId || typeof parsed.version !== 'number' || !parsed.state) {\n return null\n }\n return parsed as TourRecord\n } catch {\n return null\n }\n }\n\n async set(record: TourRecord): Promise<void> {\n await this.storage.set(storageKey(record.tourId), JSON.stringify(record))\n }\n\n async clear(tourId: string): Promise<void> {\n await this.storage.remove(storageKey(tourId))\n }\n}\n\n/** In-memory adapter. Default when no storage is configured, and handy in tests. */\nexport function createMemoryStorage(): StorageAdapter {\n const map = new Map<string, string>()\n return {\n get: (key) => map.get(key) ?? null,\n set: (key, value) => {\n map.set(key, value)\n },\n remove: (key) => {\n map.delete(key)\n },\n }\n}\n","import type { Tour } from '../schema/tour'\nimport type { DocentEvent, DocentEventType, EventSink, Identity } from '../seams'\nimport { tourVersion } from './progress'\n\nexport interface EventInput {\n tour: Tour\n identity: Identity\n stepIndex?: number\n now?: () => number\n}\n\nexport function createEvent(type: DocentEventType, input: EventInput): DocentEvent {\n const event: DocentEvent = {\n type,\n tourId: input.tour.id,\n tourVersion: tourVersion(input.tour),\n timestamp: (input.now ?? Date.now)(),\n identity: input.identity,\n }\n if (input.stepIndex !== undefined && input.stepIndex >= 0) {\n const step = input.tour.steps[input.stepIndex]\n event.stepIndex = input.stepIndex\n if (step) event.stepId = step.id\n }\n return event\n}\n\n/** Sink that drops everything. Default when none is configured. */\nexport const NOOP_SINK: EventSink = { emit() {} }\n\n/** Fan out to several sinks. */\nexport function combineSinks(...sinks: EventSink[]): EventSink {\n return {\n emit: (e) => {\n for (const s of sinks) s.emit(e)\n },\n }\n}\n","/**\n * Pure state machine for a single running tour. No side effects, no timers,\n * no DOM. Eligibility of steps (conditions, missing targets) is supplied by\n * the caller through {@link EngineContext} so the reducer stays pure.\n */\n\nimport type { Tour } from '../schema/tour'\n\nexport type TourStatus = 'idle' | 'running' | 'paused' | 'completed' | 'skipped' | 'aborted'\n\nexport interface EngineState {\n status: TourStatus\n /** Index into `tour.steps`, or -1 when not running. */\n index: number\n /** Indices of previously shown steps, oldest first. Drives `back`. */\n history: number[]\n /** Set when status is `aborted` or `paused`. */\n reason?: string\n}\n\nexport type EngineAction =\n | { type: 'start'; at?: number | string }\n | { type: 'next' }\n | { type: 'back' }\n | { type: 'go'; to: number | string }\n | { type: 'skip' }\n | { type: 'complete' }\n | { type: 'abort'; reason: string }\n | { type: 'pause'; reason: string }\n | { type: 'resume' }\n | { type: 'stepMissing' }\n | { type: 'stepSkipped' }\n\nexport interface EngineContext {\n tour: Tour\n /** Whether the step at `index` may be shown right now. */\n isEligible: (index: number) => boolean\n}\n\nexport const IDLE_STATE: EngineState = Object.freeze({ status: 'idle', index: -1, history: [] })\n\nexport function resolveStepIndex(tour: Tour, ref: number | string): number {\n if (typeof ref === 'number') return ref >= 0 && ref < tour.steps.length ? ref : -1\n return tour.steps.findIndex((s) => s.id === ref)\n}\n\n/** First eligible index at or after `from` (or at or before, when `dir` is -1). -1 if none. */\nexport function findEligible(ctx: EngineContext, from: number, dir: 1 | -1 = 1): number {\n const total = ctx.tour.steps.length\n for (let i = from; i >= 0 && i < total; i += dir) {\n if (ctx.isEligible(i)) return i\n }\n return -1\n}\n\nfunction advance(state: EngineState, ctx: EngineContext, record: boolean): EngineState {\n const next = findEligible(ctx, state.index + 1)\n if (next === -1) return { status: 'completed', index: state.index, history: state.history }\n return {\n status: 'running',\n index: next,\n history: record ? [...state.history, state.index] : state.history,\n }\n}\n\nexport function reduce(state: EngineState, action: EngineAction, ctx: EngineContext): EngineState {\n switch (action.type) {\n case 'start': {\n const at = action.at === undefined ? 0 : resolveStepIndex(ctx.tour, action.at)\n if (at === -1) return { status: 'aborted', index: -1, history: [], reason: 'unknown-step' }\n const first = findEligible(ctx, at)\n if (first === -1) {\n return { status: 'aborted', index: -1, history: [], reason: 'no-eligible-steps' }\n }\n return { status: 'running', index: first, history: [] }\n }\n\n case 'next':\n if (state.status !== 'running') return state\n return advance(state, ctx, true)\n\n case 'stepMissing': {\n if (state.status !== 'running') return state\n const step = ctx.tour.steps[state.index]\n if (step?.onMissing === 'abort') {\n return { ...state, status: 'aborted', reason: 'target-missing' }\n }\n // 'skip' and 'wait' (after the wait elapsed) both move on without recording history.\n return advance(state, ctx, false)\n }\n\n case 'stepSkipped':\n if (state.status !== 'running') return state\n return advance(state, ctx, false)\n\n case 'back': {\n if (state.status !== 'running') return state\n const history = [...state.history]\n let prev = history.pop()\n // Skip over history entries that are no longer eligible.\n while (prev !== undefined && !ctx.isEligible(prev)) prev = history.pop()\n if (prev === undefined) return state\n return { status: 'running', index: prev, history }\n }\n\n case 'go': {\n if (state.status !== 'running') return state\n const to = resolveStepIndex(ctx.tour, action.to)\n if (to === -1 || to === state.index || !ctx.isEligible(to)) return state\n return { status: 'running', index: to, history: [...state.history, state.index] }\n }\n\n case 'pause':\n if (state.status !== 'running') return state\n return { ...state, status: 'paused', reason: action.reason }\n\n case 'resume': {\n if (state.status !== 'paused') return state\n const { reason: _reason, ...rest } = state\n return { ...rest, status: 'running' }\n }\n\n case 'skip':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'skipped', index: state.index, history: state.history }\n\n case 'complete':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'completed', index: state.index, history: state.history }\n\n case 'abort':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { ...state, status: 'aborted', reason: action.reason }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Selectors\n// ---------------------------------------------------------------------------\n\nexport function isActive(state: EngineState): boolean {\n return state.status === 'running' || state.status === 'paused'\n}\n\nexport function isFinished(state: EngineState): boolean {\n return state.status === 'completed' || state.status === 'skipped' || state.status === 'aborted'\n}\n\nexport function canGoBack(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && state.history.some((i) => ctx.isEligible(i))\n}\n\n/** True when another eligible step follows. False on the last step. */\nexport function hasNext(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && findEligible(ctx, state.index + 1) !== -1\n}\n\nexport interface Progress {\n /** 1-based position of the current step. */\n current: number\n total: number\n}\n\n/** Position over all steps, ineligible ones included, so numbers stay stable. */\nexport function progress(state: EngineState, ctx: EngineContext): Progress {\n return { current: state.index + 1, total: ctx.tour.steps.length }\n}\n","/**\n * Orchestrates one tour: drives the pure reducer, talks to the renderer,\n * runs hooks, persists progress and emits events. This is the only stateful,\n * side-effectful piece of the core.\n */\n\nimport type { StepContext, TourHooks } from '../hooks'\nimport type { Step, Tour, TourProgressState } from '../schema/tour'\nimport { ANONYMOUS_IDENTITY, type EventSink, type Identity, type StorageAdapter } from '../seams'\nimport { type ConditionEnv, type CustomPredicate, evaluateCondition } from './conditions'\nimport { createEvent, NOOP_SINK } from './events'\nimport { createMemoryStorage, ProgressStore, type TourRecord, tourVersion } from './progress'\nimport {\n canGoBack,\n type EngineAction,\n type EngineContext,\n type EngineState,\n hasNext,\n IDLE_STATE,\n isFinished,\n progress,\n reduce,\n} from './reducer'\nimport type { RenderContext, Renderer } from './renderer'\nimport { matchRoute } from './route'\n\nexport interface ControllerOptions {\n tour: Tour\n renderer: Renderer\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n hooks?: TourHooks\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n /** Progress of other tours, for `tour` conditions. */\n tourState?: (tourId: string) => TourProgressState\n /** How long `onMissing: 'wait'` waits when the step sets no `waitFor`. */\n defaultWaitMs?: number\n now?: () => number\n}\n\nexport type StateListener = (state: EngineState) => void\n\nconst DEFAULT_WAIT_MS = 3000\n\nexport class TourController {\n readonly tour: Tour\n private state: EngineState = IDLE_STATE\n private readonly renderer: Renderer\n private readonly identity: Identity\n private readonly store: ProgressStore\n private readonly sink: EventSink\n private readonly hooks: TourHooks\n private readonly custom: Record<string, CustomPredicate>\n private readonly tourStateOf: ((tourId: string) => TourProgressState) | undefined\n private readonly defaultWaitMs: number\n private readonly now: () => number\n private readonly listeners = new Set<StateListener>()\n\n /** Bumped whenever an async flow must be abandoned. */\n private generation = 0\n private pendingAbort: AbortController | undefined\n private pendingTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(options: ControllerOptions) {\n this.tour = options.tour\n this.renderer = options.renderer\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.store = new ProgressStore(options.storage ?? createMemoryStorage())\n this.sink = options.sink ?? NOOP_SINK\n this.hooks = options.hooks ?? {}\n this.custom = options.custom ?? {}\n this.tourStateOf = options.tourState\n this.defaultWaitMs = options.defaultWaitMs ?? DEFAULT_WAIT_MS\n this.now = options.now ?? Date.now\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n getState(): EngineState {\n return this.state\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Start from the first eligible step, or from `at` (step id or index). */\n async start(at?: number | string): Promise<void> {\n if (this.state.status === 'running' || this.state.status === 'paused') return\n this.cancelPending()\n this.dispatch(at === undefined ? { type: 'start' } : { type: 'start', at })\n if (isFinished(this.state)) return this.finish()\n this.emit('tour:started')\n this.hooks.onStart?.(this.tour)\n await this.persist('in-progress')\n await this.showCurrent()\n }\n\n /** Start where the user left off, according to persisted progress. */\n async resume(): Promise<void> {\n const record = await this.store.get(this.tour.id)\n if (record?.state === 'in-progress' && record.version === tourVersion(this.tour)) {\n return this.start(record.stepId)\n }\n return this.start()\n }\n\n async next(): Promise<void> {\n if (this.state.status !== 'running') return\n await this.leaveCurrent()\n this.emit('step:completed', this.state.index)\n this.dispatch({ type: 'next' })\n await this.afterTransition()\n }\n\n async back(): Promise<void> {\n if (!canGoBack(this.state, this.context())) return\n await this.leaveCurrent()\n this.dispatch({ type: 'back' })\n await this.afterTransition()\n }\n\n async goTo(step: number | string): Promise<void> {\n if (this.state.status !== 'running') return\n const before = this.state\n const after = reduce(before, { type: 'go', to: step }, this.context())\n if (after === before) return\n await this.leaveCurrent()\n this.setState(after)\n await this.afterTransition()\n }\n\n /** The user gave up on the tour (Skip button, close, Escape). */\n async skip(): Promise<void> {\n if (!this.isActive()) return\n const ctx = this.stepContext()\n await this.leaveCurrent()\n this.dispatch({ type: 'skip' })\n await this.finish()\n if (ctx) this.hooks.onSkip?.(ctx)\n }\n\n async abort(reason: string): Promise<void> {\n if (!this.isActive()) return\n await this.leaveCurrent()\n this.dispatch({ type: 'abort', reason })\n await this.finish()\n }\n\n /** Report a named application event. Advances a step waiting on it. */\n notify(eventName: string): void {\n const step = this.currentStep()\n const advance = step?.advance\n if (typeof advance === 'object' && advance.on === 'event' && advance.name === eventName) {\n void this.next()\n }\n }\n\n /** Tell the controller the route changed. Pauses or resumes route-bound steps. */\n async routeChanged(): Promise<void> {\n const step = this.currentStep()\n if (!step) return\n const onRoute = this.stepOnRoute(step)\n if (this.state.status === 'paused' && this.state.reason === 'route' && onRoute) {\n this.dispatch({ type: 'resume' })\n await this.showCurrent()\n } else if (this.state.status === 'running' && !onRoute) {\n this.cancelPending()\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n }\n }\n\n /** Stop everything and clear the screen without recording an outcome. */\n async destroy(): Promise<void> {\n this.cancelPending()\n this.listeners.clear()\n // Reset before awaiting: a start() that runs while hide() settles must not\n // be overwritten afterwards (React StrictMode destroys, then reuses).\n const hidden = this.renderer.hide()\n this.state = IDLE_STATE\n await hidden\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isActive(): boolean {\n return this.state.status === 'running' || this.state.status === 'paused'\n }\n\n private currentStep(): Step | undefined {\n return this.isActive() ? this.tour.steps[this.state.index] : undefined\n }\n\n private conditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.renderer.hasTarget(t),\n custom: this.custom,\n }\n const route = this.renderer.currentRoute?.()\n if (route !== undefined) env.route = route\n if (this.tourStateOf) env.tourState = this.tourStateOf\n return env\n }\n\n private context(): EngineContext {\n const env = this.conditionEnv()\n return {\n tour: this.tour,\n isEligible: (i) => {\n const step = this.tour.steps[i]\n if (!step) return false\n return step.condition ? evaluateCondition(step.condition, env) : true\n },\n }\n }\n\n private stepOnRoute(step: Step): boolean {\n const route = this.renderer.currentRoute?.()\n if (!step.route || route === undefined) return true\n return matchRoute(step.route, route)\n }\n\n private dispatch(action: EngineAction): void {\n this.setState(reduce(this.state, action, this.context()))\n }\n\n private setState(next: EngineState): void {\n if (next === this.state) return\n this.state = next\n for (const l of this.listeners) l(next)\n }\n\n private stepContext(): StepContext | undefined {\n const step = this.currentStep()\n if (!step) return undefined\n return { tour: this.tour, step, index: this.state.index, total: this.tour.steps.length }\n }\n\n private emit(type: Parameters<typeof createEvent>[0], stepIndex?: number): void {\n const input: Parameters<typeof createEvent>[1] = {\n tour: this.tour,\n identity: this.identity,\n now: this.now,\n }\n if (stepIndex !== undefined) input.stepIndex = stepIndex\n this.sink.emit(createEvent(type, input))\n }\n\n private async persist(state: TourProgressState): Promise<void> {\n const record: TourRecord = {\n tourId: this.tour.id,\n version: tourVersion(this.tour),\n state,\n updatedAt: this.now(),\n }\n const step = this.currentStep()\n if (state === 'in-progress' && step) record.stepId = step.id\n await this.store.set(record)\n }\n\n private cancelPending(): void {\n this.generation++\n this.pendingAbort?.abort()\n this.pendingAbort = undefined\n if (this.pendingTimer !== undefined) clearTimeout(this.pendingTimer)\n this.pendingTimer = undefined\n }\n\n /** Run `beforeHide` for the step being left, if any. */\n private async leaveCurrent(): Promise<void> {\n const ctx = this.stepContext()\n this.cancelPending()\n if (ctx && this.state.status === 'running') {\n await this.hooks.steps?.[ctx.step.id]?.beforeHide?.(ctx)\n }\n }\n\n private async afterTransition(): Promise<void> {\n if (this.state.status === 'running') return this.showCurrent()\n if (isFinished(this.state)) return this.finish()\n }\n\n private async finish(): Promise<void> {\n this.cancelPending()\n await this.renderer.hide()\n switch (this.state.status) {\n case 'completed':\n this.emit('tour:completed')\n await this.persist('completed')\n this.hooks.onComplete?.(this.tour)\n break\n case 'skipped':\n this.emit('tour:skipped')\n await this.persist('skipped')\n break\n case 'aborted':\n this.emit('tour:aborted')\n await this.persist('skipped')\n this.hooks.onAbort?.(this.tour, this.state.reason ?? 'unknown')\n break\n }\n }\n\n private async showCurrent(): Promise<void> {\n this.cancelPending()\n const generation = this.generation\n const stale = () => generation !== this.generation\n const ctx = this.stepContext()\n if (!ctx) return\n const { step } = ctx\n\n if (!this.stepOnRoute(step)) {\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n return\n }\n\n const proceed = await this.hooks.steps?.[step.id]?.beforeShow?.(ctx)\n if (stale()) return\n if (proceed === false) {\n this.emit('step:skipped', this.state.index)\n this.dispatch({ type: 'stepSkipped' })\n return this.afterTransition()\n }\n\n if (step.target !== undefined && !(await this.ensureTarget(step, generation))) {\n if (stale()) return\n this.emit('step:missing', this.state.index)\n this.dispatch({ type: 'stepMissing' })\n return this.afterTransition()\n }\n if (stale()) return\n\n await this.renderer.show(this.renderContext(ctx))\n if (stale()) return\n this.emit('step:shown', this.state.index)\n this.hooks.onStepChange?.(ctx)\n await this.persist('in-progress')\n await this.hooks.steps?.[step.id]?.afterShow?.(ctx)\n if (stale()) return\n this.armAdvance(step, generation)\n }\n\n /** Resolve `true` when the target is present, waiting if the step allows it. */\n private async ensureTarget(step: Step, generation: number): Promise<boolean> {\n const target = step.target\n if (target === undefined) return true\n if (this.renderer.hasTarget(target)) return true\n const waitMs = step.waitFor ?? (step.onMissing === 'wait' ? this.defaultWaitMs : 0)\n if (waitMs <= 0) return false\n const abort = new AbortController()\n this.pendingAbort = abort\n const found = await this.renderer.waitForTarget(target, waitMs, abort.signal)\n if (generation !== this.generation) return false\n this.pendingAbort = undefined\n return found\n }\n\n /** Set up automatic advancement for `delay` and `element` steps. */\n private armAdvance(step: Step, generation: number): void {\n const advance = step.advance\n if (typeof advance !== 'object') return\n if (advance.on === 'delay') {\n this.pendingTimer = setTimeout(() => {\n this.pendingTimer = undefined\n if (generation === this.generation) void this.next()\n }, advance.ms)\n } else if (advance.on === 'element') {\n const abort = new AbortController()\n this.pendingAbort = abort\n void this.renderer\n .waitForTarget(advance.target, Number.POSITIVE_INFINITY, abort.signal)\n .then((found) => {\n if (found && generation === this.generation) void this.next()\n })\n }\n }\n\n private renderContext(ctx: StepContext): RenderContext {\n const engineCtx = this.context()\n return {\n tour: this.tour,\n step: ctx.step,\n index: ctx.index,\n progress: progress(this.state, engineCtx),\n isFirst: this.state.history.length === 0,\n isLast: !hasNext(this.state, engineCtx),\n canGoBack: canGoBack(this.state, engineCtx),\n actions: {\n next: () => void this.next(),\n back: () => void this.back(),\n skip: () => void this.skip(),\n goTo: (s) => void this.goTo(s),\n },\n }\n }\n}\n","import type { StorageAdapter } from '../seams'\n\n/**\n * Prefix every key with the user id so progress on a shared browser (or a\n * developer switching test accounts) never leaks between users.\n * Anonymous visitors use the unprefixed keys.\n */\nexport function scopeStorage(base: StorageAdapter, userId: string | undefined): StorageAdapter {\n if (!userId) return base\n const prefix = `u:${userId}:`\n return {\n get: (key) => base.get(prefix + key),\n set: (key, value) => base.set(prefix + key, value),\n remove: (key) => base.remove(prefix + key),\n }\n}\n","/**\n * The tour manager. Holds many tours, watches their triggers, checks\n * conditions and frequency, and starts at most one at a time. This is what\n * turns the rules in the tour JSON into behaviour.\n */\n\nimport { type ConditionEnv, type CustomPredicate, evaluateAll } from '../engine/conditions'\nimport type { ControllerOptions, TourController } from '../engine/controller'\nimport {\n createMemoryStorage,\n ProgressStore,\n shouldShow,\n type TourRecord,\n tourVersion,\n} from '../engine/progress'\nimport { matchRoute } from '../engine/route'\nimport type { TourHooks } from '../hooks'\nimport type { Tour, TourProgressState, TraitValue, Trigger } from '../schema/tour'\nimport {\n ANONYMOUS_IDENTITY,\n type DocentEvent,\n type EventSink,\n type Identity,\n type StorageAdapter,\n type TourSource,\n} from '../seams'\nimport type { DocentEnvironment } from './environment'\nimport { scopeStorage } from './scoped-storage'\n\n/** Options every controller the manager creates receives. */\nexport type SharedControllerOptions = Omit<ControllerOptions, 'tour' | 'renderer'>\n\nexport interface DocentOptions {\n /** Tours to manage: an array, or a source that loads (and may live-update) them. */\n tours?: Tour[] | TourSource\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n /** Hooks per tour, keyed by tour id. */\n hooks?: Record<string, TourHooks>\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n environment: DocentEnvironment\n /** Builds a platform controller for a tour. The DOM package supplies this. */\n createController: (tour: Tour, options: SharedControllerOptions) => TourController\n now?: () => number\n /**\n * Start watching triggers right away. Default true. Framework bindings pass\n * `false` and call `connect()` / `disconnect()` from their mount lifecycle,\n * which keeps construction free of side effects (React StrictMode).\n */\n connect?: boolean\n}\n\nexport interface DocentState {\n /** Id of the tour currently running or paused, if any. */\n active: string | null\n /** Tours known to the manager. */\n tours: string[]\n}\n\nexport type DocentListener = (state: DocentState) => void\n\nexport interface StartOptions {\n /** Step id or index to start from. */\n at?: number | string\n}\n\ntype Cleanup = () => void\n\nfunction isTourSource(value: Tour[] | TourSource | undefined): value is TourSource {\n return !!value && !Array.isArray(value) && typeof (value as TourSource).load === 'function'\n}\n\nexport class Docent {\n private readonly options: DocentOptions\n private readonly env: DocentEnvironment\n private readonly baseStorage: StorageAdapter\n private identity: Identity\n private store: ProgressStore\n private tours = new Map<string, Tour>()\n private records = new Map<string, TourRecord | null>()\n private controller: TourController | undefined\n private activeId: string | null = null\n /** Tours whose trigger fired while another tour was running. */\n private queue: string[] = []\n private triggerCleanups: Cleanup[] = []\n /** `auto` triggers fire once per manager instance (per page load), not on every re-arm. */\n private readonly autoFired = new Set<string>()\n private readonly cleanups: Cleanup[] = []\n private readonly timers = new Set<ReturnType<typeof setTimeout>>()\n private readonly listeners = new Set<DocentListener>()\n private readonly eventListeners = new Set<(event: DocentEvent) => void>()\n private destroyed = false\n private loading: Promise<void> | undefined\n /** Wanted by the owner (between connect and disconnect). */\n private connected = false\n /** Listening to routes, the source and triggers. */\n private attached = false\n\n constructor(options: DocentOptions) {\n this.options = options\n this.env = options.environment\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.baseStorage = options.storage ?? createMemoryStorage()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, this.identity.id))\n if (options.connect !== false) this.connect()\n }\n\n /** Resolves once tours and progress are loaded. Loading starts on first use. */\n get ready(): Promise<void> {\n this.loading ??= this.load()\n return this.loading\n }\n\n /** Start watching routes, the tour source and triggers. Idempotent. */\n connect(): void {\n if (this.destroyed || this.connected) return\n this.connected = true\n void this.ready.then(() => {\n if (this.connected && !this.attached && !this.destroyed) this.attach()\n })\n }\n\n /**\n * Stop watching and remove any running tour without recording an outcome.\n * `connect()` resumes. Unlike `destroy()`, the manager stays usable.\n */\n async disconnect(): Promise<void> {\n this.connected = false\n if (this.attached) {\n this.attached = false\n this.disarmTriggers()\n for (const c of this.cleanups) c()\n this.cleanups.length = 0\n }\n this.queue = []\n await this.stopActive()\n this.emitState()\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** The tours currently managed. */\n getTours(): Tour[] {\n return [...this.tours.values()]\n }\n\n /**\n * Observe every lifecycle event from every tour, in addition to the `sink`\n * option. Returns an unsubscribe function. Used by devtools.\n */\n onEvent(listener: (event: DocentEvent) => void): () => void {\n this.eventListeners.add(listener)\n return () => this.eventListeners.delete(listener)\n }\n\n getState(): DocentState {\n return { active: this.activeId, tours: [...this.tours.keys()] }\n }\n\n subscribe(listener: DocentListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** The controller of the running tour, for fine-grained control. */\n get activeController(): TourController | undefined {\n return this.controller\n }\n\n /**\n * Set who the user is. Progress is stored per user id, and traits feed\n * `trait` conditions. Re-evaluates triggers, since the user may now qualify.\n */\n async identify(id: string | undefined, traits: Record<string, TraitValue> = {}): Promise<void> {\n await this.ready\n const userChanged = id !== this.identity.id\n this.identity = id === undefined ? { traits } : { id, traits }\n if (userChanged) {\n this.autoFired.clear()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, id))\n await this.loadRecords()\n }\n this.armTriggers()\n }\n\n /**\n * Report something that happened in your app. Starts tours with a matching\n * `event` trigger and advances a running step waiting on that event.\n */\n track(eventName: string): void {\n this.controller?.notify(eventName)\n for (const tour of this.tours.values()) {\n const t = tour.trigger\n if (t?.type === 'event' && t.name === eventName) this.fire(tour.id)\n }\n }\n\n /**\n * Start a tour now, ignoring its trigger, conditions and frequency. Use for\n * \"take the tour\" buttons. Stops any tour already running.\n */\n async start(tourId: string, options: StartOptions = {}): Promise<boolean> {\n await this.ready\n const tour = this.tours.get(tourId)\n if (!tour) return false\n await this.stopActive()\n await this.run(tour, options.at, true)\n return true\n }\n\n /** Whether a tour's conditions hold and its frequency allows showing it now. */\n isEligible(tourId: string): boolean {\n const tour = this.tours.get(tourId)\n if (!tour) return false\n return (\n shouldShow(tour, this.records.get(tourId) ?? null) &&\n evaluateAll(tour.conditions, this.getConditionEnv())\n )\n }\n\n /** Progress state of a tour for the current user. */\n tourState(tourId: string): TourProgressState {\n const tour = this.tours.get(tourId)\n const record = this.records.get(tourId)\n if (!record) return 'not-started'\n if (tour && record.version < tourVersion(tour)) return 'not-started'\n return record.state\n }\n\n /** Forget progress for one tour, or all of them, so they show again. */\n async reset(tourId?: string): Promise<void> {\n await this.ready\n const ids = tourId ? [tourId] : [...this.tours.keys()]\n for (const id of ids) {\n await this.store.clear(id)\n this.records.set(id, null)\n this.autoFired.delete(id)\n }\n this.armTriggers()\n }\n\n /**\n * Re-check triggers and tell the running tour the route may have changed.\n * Call after navigation if your router does not emit browser navigation events.\n */\n async refresh(): Promise<void> {\n await this.ready\n this.armTriggers()\n await this.controller?.routeChanged()\n }\n\n /** Stop the running tour (recorded as skipped). */\n async stop(): Promise<void> {\n await this.controller?.skip()\n }\n\n async destroy(): Promise<void> {\n await this.disconnect()\n this.destroyed = true\n this.listeners.clear()\n this.eventListeners.clear()\n }\n\n // -------------------------------------------------------------------------\n // Loading\n // -------------------------------------------------------------------------\n\n private async load(): Promise<void> {\n const source = this.options.tours\n const initial = isTourSource(source) ? await source.load() : (source ?? [])\n this.setTours(initial)\n await this.loadRecords()\n }\n\n private attach(): void {\n this.attached = true\n const source = this.options.tours\n if (isTourSource(source) && source.subscribe) {\n this.cleanups.push(\n source.subscribe((tours) => {\n this.setTours(tours)\n void this.loadRecords().then(() => this.armTriggers())\n }),\n )\n }\n if (this.env.onRouteChange) {\n this.cleanups.push(this.env.onRouteChange(() => this.armTriggers()))\n }\n this.armTriggers()\n }\n\n private setTours(tours: Tour[]): void {\n this.tours = new Map(tours.map((t) => [t.id, t]))\n this.queue = this.queue.filter((id) => this.tours.has(id))\n this.emitState()\n }\n\n private async loadRecords(): Promise<void> {\n const entries = await Promise.all(\n [...this.tours.keys()].map(async (id) => [id, await this.store.get(id)] as const),\n )\n this.records = new Map(entries)\n }\n\n // -------------------------------------------------------------------------\n // Triggers\n // -------------------------------------------------------------------------\n\n private disarmTriggers(): void {\n for (const c of this.triggerCleanups) c()\n this.triggerCleanups = []\n for (const t of this.timers) clearTimeout(t)\n this.timers.clear()\n }\n\n /**\n * (Re)arm every trigger. Cheap; called on load, identify, route change,\n * source updates and after a tour finishes (so chained tours can start).\n * `except` skips one tour, used for the tour that just finished.\n */\n private armTriggers(except?: string): void {\n if (this.destroyed || !this.attached) return\n this.disarmTriggers()\n for (const tour of this.tours.values()) {\n const trigger = tour.trigger\n if (!trigger || tour.id === except) continue\n this.arm(tour, trigger)\n }\n }\n\n private arm(tour: Tour, trigger: Trigger): void {\n switch (trigger.type) {\n case 'manual':\n case 'event':\n return\n case 'auto':\n // Marked as used in fire() only once it actually starts or queues, so a tour\n // that was not eligible at load can still start when the user qualifies.\n if (this.autoFired.has(tour.id)) return\n this.fireAfter(tour.id, trigger.delay)\n return\n case 'route': {\n const route = this.env.currentRoute?.()\n if (route !== undefined && matchRoute(trigger.pattern, route))\n this.fireAfter(tour.id, trigger.delay)\n return\n }\n case 'element': {\n if (!this.env.watchTarget) {\n if (this.env.hasTarget(trigger.target)) this.fireAfter(tour.id, trigger.delay)\n return\n }\n this.triggerCleanups.push(\n this.env.watchTarget(trigger.target, () => this.fireAfter(tour.id, trigger.delay)),\n )\n }\n }\n }\n\n private fireAfter(tourId: string, delay: number | undefined): void {\n if (!delay) {\n this.fire(tourId)\n return\n }\n const timer = setTimeout(() => {\n this.timers.delete(timer)\n this.fire(tourId)\n }, delay)\n this.timers.add(timer)\n }\n\n /** A trigger fired: start the tour if eligible, or queue it behind the running one. */\n private fire(tourId: string): void {\n if (this.destroyed || !this.attached || tourId === this.activeId) return\n if (!this.isEligible(tourId) || !this.triggerStillHolds(tourId)) return\n if (this.tours.get(tourId)?.trigger?.type === 'auto') this.autoFired.add(tourId)\n if (this.activeId) {\n if (!this.queue.includes(tourId)) this.queue.push(tourId)\n return\n }\n const tour = this.tours.get(tourId)\n if (tour) void this.run(tour, undefined, false)\n }\n\n /** Route triggers are only valid while the user is still on a matching route. */\n private triggerStillHolds(tourId: string): boolean {\n const trigger = this.tours.get(tourId)?.trigger\n if (trigger?.type !== 'route') return true\n const route = this.env.currentRoute?.()\n return route !== undefined && matchRoute(trigger.pattern, route)\n }\n\n // -------------------------------------------------------------------------\n // Running\n // -------------------------------------------------------------------------\n\n /** What conditions are evaluated against right now. Used by `isEligible` and devtools. */\n getConditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.env.hasTarget(t),\n tourState: (id) => this.tourState(id),\n custom: this.options.custom ?? {},\n }\n const route = this.env.currentRoute?.()\n if (route !== undefined) env.route = route\n return env\n }\n\n private sharedOptions(tour: Tour): SharedControllerOptions {\n const shared: SharedControllerOptions = {\n identity: this.identity,\n storage: scopeStorage(this.baseStorage, this.identity.id),\n tourState: (id) => this.tourState(id),\n }\n shared.sink = {\n emit: (event) => {\n this.options.sink?.emit(event)\n for (const l of this.eventListeners) l(event)\n },\n }\n const hooks = this.options.hooks?.[tour.id]\n if (hooks) shared.hooks = hooks\n if (this.options.custom) shared.custom = this.options.custom\n if (this.options.now) shared.now = this.options.now\n return shared\n }\n\n private async run(tour: Tour, at: number | string | undefined, manual: boolean): Promise<void> {\n this.queue = this.queue.filter((id) => id !== tour.id)\n const controller = this.options.createController(tour, this.sharedOptions(tour))\n this.controller = controller\n this.activeId = tour.id\n // Mirror what the controller persists, so tourState() is right while it runs.\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state: 'in-progress',\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.emitState()\n\n const off = controller.subscribe((state) => {\n if (\n state.status === 'completed' ||\n state.status === 'skipped' ||\n state.status === 'aborted'\n ) {\n off()\n this.finished(controller, tour, state.status === 'completed' ? 'completed' : 'skipped')\n }\n })\n\n const resume = !manual && at === undefined && tour.options?.persist\n if (resume) await controller.resume()\n else await controller.start(at)\n\n // start() returned without running (nothing happened): release the slot.\n if (controller.getState().status === 'idle' && this.controller === controller) {\n off()\n this.release(controller)\n }\n }\n\n /**\n * The controller emits the final status before it finishes writing storage,\n * so record the outcome here directly instead of reading it back.\n */\n private finished(controller: TourController, tour: Tour, state: 'completed' | 'skipped'): void {\n if (this.controller !== controller) return\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state,\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.release(controller, tour.id)\n }\n\n private release(controller: TourController, finishedId?: string): void {\n this.controller = undefined\n this.activeId = null\n this.emitState()\n // Let the finished controller complete its own cleanup (hide, persist, events) first.\n setTimeout(() => {\n void controller.destroy()\n this.drainQueue()\n // Tours whose conditions depend on the one that just ended may now qualify.\n if (!this.activeId) this.armTriggers(finishedId)\n }, 0)\n }\n\n /** Stop the running tour without recording an outcome (used before a manual start). */\n private async stopActive(): Promise<void> {\n const controller = this.controller\n if (!controller) return\n this.controller = undefined\n this.activeId = null\n await controller.destroy()\n }\n\n private drainQueue(): void {\n while (this.queue.length > 0 && !this.activeId) {\n const next = this.queue.shift()\n if (next) this.fire(next)\n }\n }\n\n private emitState(): void {\n const state = this.getState()\n for (const l of this.listeners) l(state)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/schema/tour.ts","../src/define.ts","../src/engine/route.ts","../src/engine/conditions.ts","../src/seams.ts","../src/engine/progress.ts","../src/engine/events.ts","../src/engine/reducer.ts","../src/engine/controller.ts","../src/manager/scoped-storage.ts","../src/manager/docent.ts"],"sourcesContent":["/**\n * Tour schema — the JSON contract shared by the core engine, every renderer,\n * the visual builder and the hosted service.\n *\n * Everything in this file must stay JSON-serialisable. Functions (hooks) live\n * in `hooks.ts` and are attached at runtime, keyed by step id.\n */\n\n/** Current schema version. Bump only on breaking changes to this file. */\nexport const SCHEMA_VERSION = 1 as const\n\nexport type SchemaVersion = typeof SCHEMA_VERSION\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\n/**\n * Where a step points.\n *\n * - A bare string is a CSS selector (web only). Convenient for hand-written tours.\n * - A {@link TargetSpec} is the portable, self-healing form the builder produces.\n *\n * Prefer `{ name }` over raw selectors: on the web it resolves to\n * `[data-docent=\"<name>\"]`, on native to a `testID`, so one tour works everywhere.\n */\nexport type Target = string | TargetSpec\n\nexport interface TargetSpec {\n /**\n * Logical name. Web: `[data-docent=\"<name>\"]`. Native: `testID` / `nativeID`.\n * The most robust anchor; survives refactors and works cross-platform.\n */\n name?: string\n /**\n * Ordered CSS selector fallbacks (web only). Tried in order until one matches.\n * The builder fills several so a tour keeps working after markup changes.\n */\n selectors?: string[]\n /** Explicit native identifier when it differs from `name`. */\n native?: string\n /** Restrict the search to a container matched by this selector. */\n within?: string\n /** When a selector matches several elements, pick this index (default 0). */\n nth?: number\n}\n\n// ---------------------------------------------------------------------------\n// Placement & visuals\n// ---------------------------------------------------------------------------\n\nexport type Side = 'top' | 'right' | 'bottom' | 'left'\nexport type Alignment = 'start' | 'center' | 'end'\n\n/**\n * Preferred popover position relative to the target. `auto` lets the renderer\n * pick the side with the most room. Any placement flips or shifts when it\n * would overflow the viewport.\n */\nexport type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`\n\n/**\n * What connects the popover to its target.\n * - `caret` (default): a small notch on the popover's edge.\n * - `none`: nothing.\n * - Connectors, drawn from the popover to the target: `line`, `dashed`,\n * `dotted`, `curve`, `curve-dashed`, `squiggle`, `loop`, `elbow`, `sketch`\n * (hand-drawn double stroke) and `pin` (dotted line ending in a dot).\n */\nexport type ArrowStyle =\n | 'caret'\n | 'none'\n | 'line'\n | 'dashed'\n | 'dotted'\n | 'curve'\n | 'curve-dashed'\n | 'squiggle'\n | 'loop'\n | 'elbow'\n | 'sketch'\n | 'pin'\n\n/** Shape of the cutout around the target. `circle` circumscribes the target. */\nexport type SpotlightShape = 'rounded' | 'rect' | 'pill' | 'circle'\n\n/** Outline drawn around the cutout. `pulse` gently repeats to draw the eye. */\nexport type SpotlightRing = 'hairline' | 'none' | 'glow' | 'pulse' | 'dashed' | 'solid'\n\n/**\n * How the rest of the page is treated.\n * - `dim` (default): a tinted scrim.\n * - `blur`: scrim plus a soft blur of the page.\n * - `vignette`: clear near the target, darker toward the edges.\n * - `none`: no scrim and the page stays usable (hint-style tours).\n */\nexport type OverlayStyle = 'dim' | 'blur' | 'vignette' | 'none'\n\nexport interface SpotlightOptions {\n /** Space between the target's edge and the cutout, in px. */\n padding?: number\n /** Corner radius of the cutout, in px (for `rounded`). */\n radius?: number\n /** Animate the cutout moving between targets. */\n animate?: boolean\n shape?: SpotlightShape\n ring?: SpotlightRing\n}\n\nexport interface OverlayOptions {\n style?: OverlayStyle\n /** Backdrop colour, any CSS colour. */\n color?: string\n /** Backdrop opacity, 0–1. */\n opacity?: number\n /** Blur radius for the `blur` style, in px. */\n blur?: number\n}\n\nexport interface ScrollOptions {\n /** Scroll the target into view before showing the step. */\n enabled?: boolean\n behavior?: 'auto' | 'smooth'\n block?: 'start' | 'center' | 'end' | 'nearest'\n}\n\nexport interface Media {\n type: 'image' | 'video'\n src: string\n alt?: string\n}\n\n// ---------------------------------------------------------------------------\n// Behaviour\n// ---------------------------------------------------------------------------\n\n/**\n * How a step completes.\n *\n * - `'button'` (default): the user presses Next.\n * - `click` / `input`: the user interacts with the target (or another element).\n * - `event`: a named event is emitted through the runtime.\n * - `element`: an element appears (e.g. a menu the user was asked to open).\n * - `delay`: automatically after `ms`.\n */\nexport type Advance =\n | 'button'\n | { on: 'click'; target?: Target }\n | { on: 'input'; target?: Target; match?: string }\n | { on: 'event'; name: string }\n | { on: 'element'; target: Target }\n | { on: 'delay'; ms: number }\n\n/** Whether the user can interact with the spotlighted target. */\nexport type Interaction = 'block' | 'allow'\n\n/** What to do when a step's target cannot be found. */\nexport type OnMissing = 'skip' | 'wait' | 'abort'\n\nexport interface StepButtons {\n back?: boolean\n next?: boolean\n skip?: boolean\n close?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Conditions & triggers\n// ---------------------------------------------------------------------------\n\nexport type TraitValue = string | number | boolean | null | string[]\n\nexport type TraitOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'in'\n | 'nin'\n | 'contains'\n | 'exists'\n | 'missing'\n\n/**\n * Serialisable predicate evaluated by the core at runtime.\n * `custom` predicates are registered by name on the runtime.\n */\nexport type Condition =\n | { type: 'trait'; key: string; op: TraitOperator; value?: TraitValue }\n | { type: 'route'; pattern: string }\n | { type: 'element'; target: Target; exists?: boolean }\n | { type: 'tour'; id: string; state: TourProgressState }\n | { type: 'all'; conditions: Condition[] }\n | { type: 'any'; conditions: Condition[] }\n | { type: 'not'; condition: Condition }\n | { type: 'custom'; name: string; args?: Record<string, TraitValue> }\n\nexport type TourProgressState = 'not-started' | 'in-progress' | 'completed' | 'skipped'\n\n/** What starts a tour. `manual` means only through the runtime API. */\nexport type Trigger =\n | { type: 'manual' }\n | { type: 'auto'; delay?: number }\n | { type: 'route'; pattern: string; delay?: number }\n | { type: 'element'; target: Target; delay?: number }\n | { type: 'event'; name: string }\n\n/**\n * How often an eligible user sees the tour.\n * - `once`: show once per {@link Tour.version}, however it ended.\n * - `until-completed`: keep offering it until the user finishes it.\n * - `always`: every time the trigger fires.\n */\nexport type Frequency = 'once' | 'until-completed' | 'always'\n\n// ---------------------------------------------------------------------------\n// Theme\n// ---------------------------------------------------------------------------\n\n/**\n * Visual tokens, serialisable so a builder or customizer can produce them.\n * Each maps to a CSS custom property in the renderer (`--docent-*`).\n * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.\n */\nexport interface Theme {\n /** Color of drawn connectors (arrow styles other than caret). */\n connector?: string\n /** Color of the spotlight ring. */\n ring?: string\n background?: string\n foreground?: string\n muted?: string\n accent?: string\n accentForeground?: string\n radius?: string\n shadow?: string\n font?: string\n width?: string\n overlay?: string\n overlayOpacity?: string\n duration?: string\n zIndex?: string\n}\n\n// ---------------------------------------------------------------------------\n// Labels\n// ---------------------------------------------------------------------------\n\nexport interface Labels {\n next?: string\n back?: string\n skip?: string\n done?: string\n close?: string\n /** Supports `{current}` and `{total}` placeholders. */\n progress?: string\n}\n\n// ---------------------------------------------------------------------------\n// Step\n// ---------------------------------------------------------------------------\n\nexport interface Step {\n /** Unique within the tour. Used for persistence, hooks and analytics. */\n id: string\n /** Omit for a centred modal step (welcome / finish screens). */\n target?: Target\n title?: string\n body?: string\n /** How `body` is interpreted. Renderers never inject raw HTML. */\n format?: 'text' | 'markdown'\n media?: Media\n placement?: Placement\n /** Per-step override of the tour's arrow style. */\n arrow?: ArrowStyle\n /** Per-step override of the tour's spotlight options. */\n spotlight?: SpotlightOptions\n /** Per-step override of the tour's overlay options. */\n overlay?: OverlayOptions\n advance?: Advance\n interaction?: Interaction\n /** Skip this step when the condition is false. */\n condition?: Condition\n onMissing?: OnMissing\n /** How long to wait for the target when `onMissing` is `wait`, in ms. */\n waitFor?: number\n /** URL pattern this step belongs to. Enables multi-page tours. */\n route?: string\n buttons?: StepButtons\n scroll?: ScrollOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Tour\n// ---------------------------------------------------------------------------\n\nexport interface TourOptions {\n /** Persist progress so the tour survives navigation and reloads. */\n persist?: boolean\n frequency?: Frequency\n showProgress?: boolean\n /** Allow closing with Escape or the close button. */\n allowClose?: boolean\n closeOnOverlayClick?: boolean\n keyboard?: boolean\n arrow?: ArrowStyle\n spotlight?: SpotlightOptions\n overlay?: OverlayOptions\n scroll?: ScrollOptions\n labels?: Labels\n /** Visual tokens applied on top of the renderer's theme. */\n theme?: Theme\n /** Name of a template registered on the renderer (slots, css, theme). */\n template?: string\n}\n\nexport interface Tour {\n schemaVersion: SchemaVersion\n /** Stable identifier. Used for persistence, targeting and analytics. */\n id: string\n /** Bump to re-show the tour to users who already saw an older version. */\n version?: number\n /** Human-readable name, mainly for the builder and dashboards. */\n name?: string\n description?: string\n steps: Step[]\n trigger?: Trigger\n /** All must hold for the tour to be eligible. */\n conditions?: Condition[]\n options?: TourOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n","import { SCHEMA_VERSION, type SchemaVersion, type Tour } from './schema/tour'\n\n/**\n * Identity helper that gives hand-written tours full type inference and\n * fills in the schema version. Returns the same object.\n */\nexport function defineTour(\n tour: Omit<Tour, 'schemaVersion'> & { schemaVersion?: SchemaVersion },\n): Tour {\n return { ...tour, schemaVersion: SCHEMA_VERSION }\n}\n","/**\n * Route pattern matching for `route` triggers, conditions and step routes.\n *\n * Patterns are path globs:\n * - `/settings` exact\n * - `/users/:id` one segment (named for readability, value ignored)\n * - `/users/*` one segment\n * - `/docs/**` zero or more segments\n *\n * Query strings and hashes are ignored. Trailing slashes are tolerated.\n */\n\nfunction segments(path: string): string[] {\n const clean = path.split(/[?#]/, 1)[0] ?? ''\n return clean.split('/').filter(Boolean)\n}\n\nfunction matchSegments(pattern: string[], path: string[], pi = 0, si = 0): boolean {\n if (pi === pattern.length) return si === path.length\n const p = pattern[pi]\n if (p === '**') {\n for (let k = si; k <= path.length; k++) {\n if (matchSegments(pattern, path, pi + 1, k)) return true\n }\n return false\n }\n if (si === path.length) return false\n if (p === '*' || p?.startsWith(':') || p === path[si]) {\n return matchSegments(pattern, path, pi + 1, si + 1)\n }\n return false\n}\n\nexport function matchRoute(pattern: string, path: string): boolean {\n return matchSegments(segments(pattern), segments(path))\n}\n","/**\n * Evaluates serialisable {@link Condition}s against a runtime environment.\n * Pure: everything it needs is passed in.\n */\n\nimport type { Condition, Target, TourProgressState, TraitValue } from '../schema/tour'\nimport type { Identity } from '../seams'\nimport { matchRoute } from './route'\n\nexport type CustomPredicate = (args?: Record<string, TraitValue>) => boolean\n\nexport interface ConditionEnv {\n identity: Identity\n /** Current path, e.g. `/invoices/new`. */\n route?: string\n elementExists?: (target: Target) => boolean\n tourState?: (tourId: string) => TourProgressState\n custom?: Record<string, CustomPredicate>\n}\n\nfunction sameValue(a: TraitValue | undefined, b: TraitValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => v === b[i])\n }\n return a === b\n}\n\nfunction compare(a: TraitValue | undefined, b: TraitValue | undefined): number | null {\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n return null\n}\n\nfunction contains(haystack: TraitValue | undefined, needle: TraitValue | undefined): boolean {\n if (Array.isArray(haystack)) return typeof needle === 'string' && haystack.includes(needle)\n if (typeof haystack === 'string') {\n return (\n (typeof needle === 'string' || typeof needle === 'number') &&\n haystack.includes(String(needle))\n )\n }\n return false\n}\n\nfunction inList(value: TraitValue | undefined, list: TraitValue | undefined): boolean {\n if (!Array.isArray(list)) return false\n if (Array.isArray(value)) return value.some((v) => list.includes(v))\n return typeof value === 'string' && list.includes(value)\n}\n\nexport function evaluateTrait(\n actual: TraitValue | undefined,\n op: Extract<Condition, { type: 'trait' }>['op'],\n expected: TraitValue | undefined,\n): boolean {\n switch (op) {\n case 'exists':\n return actual !== undefined && actual !== null\n case 'missing':\n return actual === undefined || actual === null\n case 'eq':\n return sameValue(actual, expected)\n case 'neq':\n return !sameValue(actual, expected)\n case 'in':\n return inList(actual, expected)\n case 'nin':\n return !inList(actual, expected)\n case 'contains':\n return contains(actual, expected)\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const c = compare(actual, expected)\n if (c === null) return false\n if (op === 'gt') return c > 0\n if (op === 'gte') return c >= 0\n if (op === 'lt') return c < 0\n return c <= 0\n }\n }\n}\n\nexport function evaluateCondition(condition: Condition, env: ConditionEnv): boolean {\n switch (condition.type) {\n case 'trait':\n return evaluateTrait(env.identity.traits[condition.key], condition.op, condition.value)\n case 'route':\n return env.route !== undefined && matchRoute(condition.pattern, env.route)\n case 'element': {\n const exists = env.elementExists?.(condition.target) ?? false\n return exists === (condition.exists ?? true)\n }\n case 'tour':\n return (env.tourState?.(condition.id) ?? 'not-started') === condition.state\n case 'all':\n return condition.conditions.every((c) => evaluateCondition(c, env))\n case 'any':\n return condition.conditions.some((c) => evaluateCondition(c, env))\n case 'not':\n return !evaluateCondition(condition.condition, env)\n case 'custom':\n return env.custom?.[condition.name]?.(condition.args) ?? false\n }\n}\n\n/** All conditions must hold. An empty or missing list holds. */\nexport function evaluateAll(conditions: Condition[] | undefined, env: ConditionEnv): boolean {\n return (conditions ?? []).every((c) => evaluateCondition(c, env))\n}\n","/**\n * Extension seams — the interfaces the runtime is built against so that tours,\n * user identity, storage and analytics can come from anywhere: inline JSON,\n * a local file, the user's own backend, or a hosted service.\n */\n\nimport type { Tour, TraitValue } from './schema/tour'\n\nexport type MaybePromise<T> = T | Promise<T>\n\n// ---------------------------------------------------------------------------\n// Tour source\n// ---------------------------------------------------------------------------\n\nexport interface TourSource {\n /** Return every tour this source knows about. */\n load(): MaybePromise<Tour[]>\n /**\n * Optional live updates. Return an unsubscribe function.\n * Lets a hosted source push tour changes without a page reload.\n */\n subscribe?(listener: (tours: Tour[]) => void): () => void\n}\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\nexport interface Identity {\n /** Stable user id. Omit for anonymous visitors. */\n id?: string\n /** Attributes that `trait` conditions evaluate against. */\n traits: Record<string, TraitValue>\n}\n\nexport const ANONYMOUS_IDENTITY: Identity = { traits: {} }\n\n// ---------------------------------------------------------------------------\n// Storage\n// ---------------------------------------------------------------------------\n\n/**\n * Key/value storage for progress and seen-state. Shape matches `localStorage`\n * but every method may be async so React Native's AsyncStorage fits too.\n */\nexport interface StorageAdapter {\n get(key: string): MaybePromise<string | null>\n set(key: string, value: string): MaybePromise<void>\n remove(key: string): MaybePromise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DocentEventType =\n | 'tour:started'\n | 'tour:completed'\n | 'tour:skipped'\n | 'tour:aborted'\n | 'step:shown'\n | 'step:completed'\n | 'step:skipped'\n | 'step:missing'\n\nexport interface DocentEvent {\n type: DocentEventType\n tourId: string\n tourVersion: number\n stepId?: string\n stepIndex?: number\n /** Unix epoch milliseconds. */\n timestamp: number\n identity: Identity\n}\n\n/** Receives every lifecycle event. Wire it to console, your analytics, or a hosted endpoint. */\nexport interface EventSink {\n emit(event: DocentEvent): void\n}\n","/**\n * Persisted per-tour progress: what the user has seen, where they stopped,\n * and whether the tour should be offered again.\n */\n\nimport type { Frequency, Tour, TourProgressState } from '../schema/tour'\nimport type { StorageAdapter } from '../seams'\n\nexport interface TourRecord {\n tourId: string\n version: number\n state: TourProgressState\n /** Step to resume from when the tour was interrupted. */\n stepId?: string\n /** Unix epoch milliseconds. */\n updatedAt: number\n}\n\nexport const STORAGE_PREFIX = 'docent:'\n\nexport function storageKey(tourId: string): string {\n return `${STORAGE_PREFIX}${tourId}`\n}\n\nexport function tourVersion(tour: Tour): number {\n return tour.version ?? 1\n}\n\n/** Whether a tour should be offered given what the user has already done. */\nexport function shouldShow(tour: Tour, record: TourRecord | null): boolean {\n if (!record) return true\n if (record.version < tourVersion(tour)) return true\n const frequency: Frequency = tour.options?.frequency ?? 'once'\n switch (frequency) {\n case 'always':\n return true\n case 'until-completed':\n return record.state !== 'completed'\n case 'once':\n return record.state === 'not-started' || record.state === 'in-progress'\n }\n}\n\nexport class ProgressStore {\n constructor(private readonly storage: StorageAdapter) {}\n\n async get(tourId: string): Promise<TourRecord | null> {\n const raw = await this.storage.get(storageKey(tourId))\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<TourRecord>\n if (parsed.tourId !== tourId || typeof parsed.version !== 'number' || !parsed.state) {\n return null\n }\n return parsed as TourRecord\n } catch {\n return null\n }\n }\n\n async set(record: TourRecord): Promise<void> {\n await this.storage.set(storageKey(record.tourId), JSON.stringify(record))\n }\n\n async clear(tourId: string): Promise<void> {\n await this.storage.remove(storageKey(tourId))\n }\n}\n\n/** In-memory adapter. Default when no storage is configured, and handy in tests. */\nexport function createMemoryStorage(): StorageAdapter {\n const map = new Map<string, string>()\n return {\n get: (key) => map.get(key) ?? null,\n set: (key, value) => {\n map.set(key, value)\n },\n remove: (key) => {\n map.delete(key)\n },\n }\n}\n","import type { Tour } from '../schema/tour'\nimport type { DocentEvent, DocentEventType, EventSink, Identity } from '../seams'\nimport { tourVersion } from './progress'\n\nexport interface EventInput {\n tour: Tour\n identity: Identity\n stepIndex?: number\n now?: () => number\n}\n\nexport function createEvent(type: DocentEventType, input: EventInput): DocentEvent {\n const event: DocentEvent = {\n type,\n tourId: input.tour.id,\n tourVersion: tourVersion(input.tour),\n timestamp: (input.now ?? Date.now)(),\n identity: input.identity,\n }\n if (input.stepIndex !== undefined && input.stepIndex >= 0) {\n const step = input.tour.steps[input.stepIndex]\n event.stepIndex = input.stepIndex\n if (step) event.stepId = step.id\n }\n return event\n}\n\n/** Sink that drops everything. Default when none is configured. */\nexport const NOOP_SINK: EventSink = { emit() {} }\n\n/** Fan out to several sinks. */\nexport function combineSinks(...sinks: EventSink[]): EventSink {\n return {\n emit: (e) => {\n for (const s of sinks) s.emit(e)\n },\n }\n}\n","/**\n * Pure state machine for a single running tour. No side effects, no timers,\n * no DOM. Eligibility of steps (conditions, missing targets) is supplied by\n * the caller through {@link EngineContext} so the reducer stays pure.\n */\n\nimport type { Tour } from '../schema/tour'\n\nexport type TourStatus = 'idle' | 'running' | 'paused' | 'completed' | 'skipped' | 'aborted'\n\nexport interface EngineState {\n status: TourStatus\n /** Index into `tour.steps`, or -1 when not running. */\n index: number\n /** Indices of previously shown steps, oldest first. Drives `back`. */\n history: number[]\n /** Set when status is `aborted` or `paused`. */\n reason?: string\n}\n\nexport type EngineAction =\n | { type: 'start'; at?: number | string }\n | { type: 'next' }\n | { type: 'back' }\n | { type: 'go'; to: number | string }\n | { type: 'skip' }\n | { type: 'complete' }\n | { type: 'abort'; reason: string }\n | { type: 'pause'; reason: string }\n | { type: 'resume' }\n | { type: 'stepMissing' }\n | { type: 'stepSkipped' }\n\nexport interface EngineContext {\n tour: Tour\n /** Whether the step at `index` may be shown right now. */\n isEligible: (index: number) => boolean\n}\n\nexport const IDLE_STATE: EngineState = Object.freeze({ status: 'idle', index: -1, history: [] })\n\nexport function resolveStepIndex(tour: Tour, ref: number | string): number {\n if (typeof ref === 'number') return ref >= 0 && ref < tour.steps.length ? ref : -1\n return tour.steps.findIndex((s) => s.id === ref)\n}\n\n/** First eligible index at or after `from` (or at or before, when `dir` is -1). -1 if none. */\nexport function findEligible(ctx: EngineContext, from: number, dir: 1 | -1 = 1): number {\n const total = ctx.tour.steps.length\n for (let i = from; i >= 0 && i < total; i += dir) {\n if (ctx.isEligible(i)) return i\n }\n return -1\n}\n\nfunction advance(state: EngineState, ctx: EngineContext, record: boolean): EngineState {\n const next = findEligible(ctx, state.index + 1)\n if (next === -1) return { status: 'completed', index: state.index, history: state.history }\n return {\n status: 'running',\n index: next,\n history: record ? [...state.history, state.index] : state.history,\n }\n}\n\nexport function reduce(state: EngineState, action: EngineAction, ctx: EngineContext): EngineState {\n switch (action.type) {\n case 'start': {\n const at = action.at === undefined ? 0 : resolveStepIndex(ctx.tour, action.at)\n if (at === -1) return { status: 'aborted', index: -1, history: [], reason: 'unknown-step' }\n const first = findEligible(ctx, at)\n if (first === -1) {\n return { status: 'aborted', index: -1, history: [], reason: 'no-eligible-steps' }\n }\n return { status: 'running', index: first, history: [] }\n }\n\n case 'next':\n if (state.status !== 'running') return state\n return advance(state, ctx, true)\n\n case 'stepMissing': {\n if (state.status !== 'running') return state\n const step = ctx.tour.steps[state.index]\n if (step?.onMissing === 'abort') {\n return { ...state, status: 'aborted', reason: 'target-missing' }\n }\n // 'skip' and 'wait' (after the wait elapsed) both move on without recording history.\n return advance(state, ctx, false)\n }\n\n case 'stepSkipped':\n if (state.status !== 'running') return state\n return advance(state, ctx, false)\n\n case 'back': {\n if (state.status !== 'running') return state\n const history = [...state.history]\n let prev = history.pop()\n // Skip over history entries that are no longer eligible.\n while (prev !== undefined && !ctx.isEligible(prev)) prev = history.pop()\n if (prev === undefined) return state\n return { status: 'running', index: prev, history }\n }\n\n case 'go': {\n if (state.status !== 'running') return state\n const to = resolveStepIndex(ctx.tour, action.to)\n if (to === -1 || to === state.index || !ctx.isEligible(to)) return state\n return { status: 'running', index: to, history: [...state.history, state.index] }\n }\n\n case 'pause':\n if (state.status !== 'running') return state\n return { ...state, status: 'paused', reason: action.reason }\n\n case 'resume': {\n if (state.status !== 'paused') return state\n const { reason: _reason, ...rest } = state\n return { ...rest, status: 'running' }\n }\n\n case 'skip':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'skipped', index: state.index, history: state.history }\n\n case 'complete':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'completed', index: state.index, history: state.history }\n\n case 'abort':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { ...state, status: 'aborted', reason: action.reason }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Selectors\n// ---------------------------------------------------------------------------\n\nexport function isActive(state: EngineState): boolean {\n return state.status === 'running' || state.status === 'paused'\n}\n\nexport function isFinished(state: EngineState): boolean {\n return state.status === 'completed' || state.status === 'skipped' || state.status === 'aborted'\n}\n\nexport function canGoBack(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && state.history.some((i) => ctx.isEligible(i))\n}\n\n/** True when another eligible step follows. False on the last step. */\nexport function hasNext(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && findEligible(ctx, state.index + 1) !== -1\n}\n\nexport interface Progress {\n /** 1-based position of the current step. */\n current: number\n total: number\n}\n\n/** Position over all steps, ineligible ones included, so numbers stay stable. */\nexport function progress(state: EngineState, ctx: EngineContext): Progress {\n return { current: state.index + 1, total: ctx.tour.steps.length }\n}\n","/**\n * Orchestrates one tour: drives the pure reducer, talks to the renderer,\n * runs hooks, persists progress and emits events. This is the only stateful,\n * side-effectful piece of the core.\n */\n\nimport type { StepContext, TourHooks } from '../hooks'\nimport type { Step, Tour, TourProgressState } from '../schema/tour'\nimport { ANONYMOUS_IDENTITY, type EventSink, type Identity, type StorageAdapter } from '../seams'\nimport { type ConditionEnv, type CustomPredicate, evaluateCondition } from './conditions'\nimport { createEvent, NOOP_SINK } from './events'\nimport { createMemoryStorage, ProgressStore, type TourRecord, tourVersion } from './progress'\nimport {\n canGoBack,\n type EngineAction,\n type EngineContext,\n type EngineState,\n hasNext,\n IDLE_STATE,\n isFinished,\n progress,\n reduce,\n} from './reducer'\nimport type { RenderContext, Renderer } from './renderer'\nimport { matchRoute } from './route'\n\nexport interface ControllerOptions {\n tour: Tour\n renderer: Renderer\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n hooks?: TourHooks\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n /** Progress of other tours, for `tour` conditions. */\n tourState?: (tourId: string) => TourProgressState\n /** How long `onMissing: 'wait'` waits when the step sets no `waitFor`. */\n defaultWaitMs?: number\n now?: () => number\n}\n\nexport type StateListener = (state: EngineState) => void\n\nconst DEFAULT_WAIT_MS = 3000\n\nexport class TourController {\n tour: Tour\n private state: EngineState = IDLE_STATE\n private readonly renderer: Renderer\n private readonly identity: Identity\n private readonly store: ProgressStore\n private readonly sink: EventSink\n private readonly hooks: TourHooks\n private readonly custom: Record<string, CustomPredicate>\n private readonly tourStateOf: ((tourId: string) => TourProgressState) | undefined\n private readonly defaultWaitMs: number\n private readonly now: () => number\n private readonly listeners = new Set<StateListener>()\n\n /** Bumped whenever an async flow must be abandoned. */\n private generation = 0\n private pendingAbort: AbortController | undefined\n private pendingTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(options: ControllerOptions) {\n this.tour = options.tour\n this.renderer = options.renderer\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.store = new ProgressStore(options.storage ?? createMemoryStorage())\n this.sink = options.sink ?? NOOP_SINK\n this.hooks = options.hooks ?? {}\n this.custom = options.custom ?? {}\n this.tourStateOf = options.tourState\n this.defaultWaitMs = options.defaultWaitMs ?? DEFAULT_WAIT_MS\n this.now = options.now ?? Date.now\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n getState(): EngineState {\n return this.state\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Start from the first eligible step, or from `at` (step id or index). */\n async start(at?: number | string): Promise<void> {\n if (this.state.status === 'running' || this.state.status === 'paused') return\n this.cancelPending()\n this.dispatch(at === undefined ? { type: 'start' } : { type: 'start', at })\n if (isFinished(this.state)) return this.finish()\n this.emit('tour:started')\n this.hooks.onStart?.(this.tour)\n await this.persist('in-progress')\n await this.showCurrent()\n }\n\n /** Start where the user left off, according to persisted progress. */\n async resume(): Promise<void> {\n const record = await this.store.get(this.tour.id)\n if (record?.state === 'in-progress' && record.version === tourVersion(this.tour)) {\n return this.start(record.stepId)\n }\n return this.start()\n }\n\n async next(): Promise<void> {\n if (this.state.status !== 'running') return\n await this.leaveCurrent()\n this.emit('step:completed', this.state.index)\n this.dispatch({ type: 'next' })\n await this.afterTransition()\n }\n\n async back(): Promise<void> {\n if (!canGoBack(this.state, this.context())) return\n await this.leaveCurrent()\n this.dispatch({ type: 'back' })\n await this.afterTransition()\n }\n\n async goTo(step: number | string): Promise<void> {\n if (this.state.status !== 'running') return\n const before = this.state\n const after = reduce(before, { type: 'go', to: step }, this.context())\n if (after === before) return\n await this.leaveCurrent()\n this.setState(after)\n await this.afterTransition()\n }\n\n /** The user gave up on the tour (Skip button, close, Escape). */\n async skip(): Promise<void> {\n if (!this.isActive()) return\n const ctx = this.stepContext()\n await this.leaveCurrent()\n this.dispatch({ type: 'skip' })\n await this.finish()\n if (ctx) this.hooks.onSkip?.(ctx)\n }\n\n async abort(reason: string): Promise<void> {\n if (!this.isActive()) return\n await this.leaveCurrent()\n this.dispatch({ type: 'abort', reason })\n await this.finish()\n }\n\n /**\n * Swap in a new definition of this tour (live editing). A running tour\n * re-renders its current step, or the nearest one if that step was removed.\n */\n async updateTour(tour: Tour): Promise<void> {\n const currentId = this.currentStep()?.id\n this.tour = tour\n if (!this.isActive()) return\n let index = currentId === undefined ? -1 : tour.steps.findIndex((s) => s.id === currentId)\n if (index === -1) index = Math.min(this.state.index, tour.steps.length - 1)\n if (index < 0) {\n await this.abort('tour-emptied')\n return\n }\n const history = this.state.history.filter((i) => i < tour.steps.length && i !== index)\n this.setState({ ...this.state, index, history })\n if (this.state.status === 'running') await this.showCurrent()\n }\n\n /** Report a named application event. Advances a step waiting on it. */\n notify(eventName: string): void {\n const step = this.currentStep()\n const advance = step?.advance\n if (typeof advance === 'object' && advance.on === 'event' && advance.name === eventName) {\n void this.next()\n }\n }\n\n /** Tell the controller the route changed. Pauses or resumes route-bound steps. */\n async routeChanged(): Promise<void> {\n const step = this.currentStep()\n if (!step) return\n const onRoute = this.stepOnRoute(step)\n if (this.state.status === 'paused' && this.state.reason === 'route' && onRoute) {\n this.dispatch({ type: 'resume' })\n await this.showCurrent()\n } else if (this.state.status === 'running' && !onRoute) {\n this.cancelPending()\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n }\n }\n\n /** Stop everything and clear the screen without recording an outcome. */\n async destroy(): Promise<void> {\n this.cancelPending()\n this.listeners.clear()\n // Reset before awaiting: a start() that runs while hide() settles must not\n // be overwritten afterwards (React StrictMode destroys, then reuses).\n const hidden = this.renderer.hide()\n this.state = IDLE_STATE\n await hidden\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isActive(): boolean {\n return this.state.status === 'running' || this.state.status === 'paused'\n }\n\n private currentStep(): Step | undefined {\n return this.isActive() ? this.tour.steps[this.state.index] : undefined\n }\n\n private conditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.renderer.hasTarget(t),\n custom: this.custom,\n }\n const route = this.renderer.currentRoute?.()\n if (route !== undefined) env.route = route\n if (this.tourStateOf) env.tourState = this.tourStateOf\n return env\n }\n\n private context(): EngineContext {\n const env = this.conditionEnv()\n return {\n tour: this.tour,\n isEligible: (i) => {\n const step = this.tour.steps[i]\n if (!step) return false\n return step.condition ? evaluateCondition(step.condition, env) : true\n },\n }\n }\n\n private stepOnRoute(step: Step): boolean {\n const route = this.renderer.currentRoute?.()\n if (!step.route || route === undefined) return true\n return matchRoute(step.route, route)\n }\n\n private dispatch(action: EngineAction): void {\n this.setState(reduce(this.state, action, this.context()))\n }\n\n private setState(next: EngineState): void {\n if (next === this.state) return\n this.state = next\n for (const l of this.listeners) l(next)\n }\n\n private stepContext(): StepContext | undefined {\n const step = this.currentStep()\n if (!step) return undefined\n return { tour: this.tour, step, index: this.state.index, total: this.tour.steps.length }\n }\n\n private emit(type: Parameters<typeof createEvent>[0], stepIndex?: number): void {\n const input: Parameters<typeof createEvent>[1] = {\n tour: this.tour,\n identity: this.identity,\n now: this.now,\n }\n if (stepIndex !== undefined) input.stepIndex = stepIndex\n this.sink.emit(createEvent(type, input))\n }\n\n private async persist(state: TourProgressState): Promise<void> {\n const record: TourRecord = {\n tourId: this.tour.id,\n version: tourVersion(this.tour),\n state,\n updatedAt: this.now(),\n }\n const step = this.currentStep()\n if (state === 'in-progress' && step) record.stepId = step.id\n await this.store.set(record)\n }\n\n private cancelPending(): void {\n this.generation++\n this.pendingAbort?.abort()\n this.pendingAbort = undefined\n if (this.pendingTimer !== undefined) clearTimeout(this.pendingTimer)\n this.pendingTimer = undefined\n }\n\n /** Run `beforeHide` for the step being left, if any. */\n private async leaveCurrent(): Promise<void> {\n const ctx = this.stepContext()\n this.cancelPending()\n if (ctx && this.state.status === 'running') {\n await this.hooks.steps?.[ctx.step.id]?.beforeHide?.(ctx)\n }\n }\n\n private async afterTransition(): Promise<void> {\n if (this.state.status === 'running') return this.showCurrent()\n if (isFinished(this.state)) return this.finish()\n }\n\n private async finish(): Promise<void> {\n this.cancelPending()\n await this.renderer.hide()\n switch (this.state.status) {\n case 'completed':\n this.emit('tour:completed')\n await this.persist('completed')\n this.hooks.onComplete?.(this.tour)\n break\n case 'skipped':\n this.emit('tour:skipped')\n await this.persist('skipped')\n break\n case 'aborted':\n this.emit('tour:aborted')\n await this.persist('skipped')\n this.hooks.onAbort?.(this.tour, this.state.reason ?? 'unknown')\n break\n }\n }\n\n private async showCurrent(): Promise<void> {\n this.cancelPending()\n const generation = this.generation\n const stale = () => generation !== this.generation\n const ctx = this.stepContext()\n if (!ctx) return\n const { step } = ctx\n\n if (!this.stepOnRoute(step)) {\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n return\n }\n\n const proceed = await this.hooks.steps?.[step.id]?.beforeShow?.(ctx)\n if (stale()) return\n if (proceed === false) {\n this.emit('step:skipped', this.state.index)\n this.dispatch({ type: 'stepSkipped' })\n return this.afterTransition()\n }\n\n if (step.target !== undefined && !(await this.ensureTarget(step, generation))) {\n if (stale()) return\n this.emit('step:missing', this.state.index)\n this.dispatch({ type: 'stepMissing' })\n return this.afterTransition()\n }\n if (stale()) return\n\n await this.renderer.show(this.renderContext(ctx))\n if (stale()) return\n this.emit('step:shown', this.state.index)\n this.hooks.onStepChange?.(ctx)\n await this.persist('in-progress')\n await this.hooks.steps?.[step.id]?.afterShow?.(ctx)\n if (stale()) return\n this.armAdvance(step, generation)\n }\n\n /** Resolve `true` when the target is present, waiting if the step allows it. */\n private async ensureTarget(step: Step, generation: number): Promise<boolean> {\n const target = step.target\n if (target === undefined) return true\n if (this.renderer.hasTarget(target)) return true\n const waitMs = step.waitFor ?? (step.onMissing === 'wait' ? this.defaultWaitMs : 0)\n if (waitMs <= 0) return false\n const abort = new AbortController()\n this.pendingAbort = abort\n const found = await this.renderer.waitForTarget(target, waitMs, abort.signal)\n if (generation !== this.generation) return false\n this.pendingAbort = undefined\n return found\n }\n\n /** Set up automatic advancement for `delay` and `element` steps. */\n private armAdvance(step: Step, generation: number): void {\n const advance = step.advance\n if (typeof advance !== 'object') return\n if (advance.on === 'delay') {\n this.pendingTimer = setTimeout(() => {\n this.pendingTimer = undefined\n if (generation === this.generation) void this.next()\n }, advance.ms)\n } else if (advance.on === 'element') {\n const abort = new AbortController()\n this.pendingAbort = abort\n void this.renderer\n .waitForTarget(advance.target, Number.POSITIVE_INFINITY, abort.signal)\n .then((found) => {\n if (found && generation === this.generation) void this.next()\n })\n }\n }\n\n private renderContext(ctx: StepContext): RenderContext {\n const engineCtx = this.context()\n return {\n tour: this.tour,\n step: ctx.step,\n index: ctx.index,\n progress: progress(this.state, engineCtx),\n isFirst: this.state.history.length === 0,\n isLast: !hasNext(this.state, engineCtx),\n canGoBack: canGoBack(this.state, engineCtx),\n actions: {\n next: () => void this.next(),\n back: () => void this.back(),\n skip: () => void this.skip(),\n goTo: (s) => void this.goTo(s),\n },\n }\n }\n}\n","import type { StorageAdapter } from '../seams'\n\n/**\n * Prefix every key with the user id so progress on a shared browser (or a\n * developer switching test accounts) never leaks between users.\n * Anonymous visitors use the unprefixed keys.\n */\nexport function scopeStorage(base: StorageAdapter, userId: string | undefined): StorageAdapter {\n if (!userId) return base\n const prefix = `u:${userId}:`\n return {\n get: (key) => base.get(prefix + key),\n set: (key, value) => base.set(prefix + key, value),\n remove: (key) => base.remove(prefix + key),\n }\n}\n","/**\n * The tour manager. Holds many tours, watches their triggers, checks\n * conditions and frequency, and starts at most one at a time. This is what\n * turns the rules in the tour JSON into behaviour.\n */\n\nimport { type ConditionEnv, type CustomPredicate, evaluateAll } from '../engine/conditions'\nimport type { ControllerOptions, TourController } from '../engine/controller'\nimport {\n createMemoryStorage,\n ProgressStore,\n shouldShow,\n type TourRecord,\n tourVersion,\n} from '../engine/progress'\nimport { matchRoute } from '../engine/route'\nimport type { TourHooks } from '../hooks'\nimport type { Tour, TourProgressState, TraitValue, Trigger } from '../schema/tour'\nimport {\n ANONYMOUS_IDENTITY,\n type DocentEvent,\n type EventSink,\n type Identity,\n type StorageAdapter,\n type TourSource,\n} from '../seams'\nimport type { DocentEnvironment } from './environment'\nimport { scopeStorage } from './scoped-storage'\n\n/** Options every controller the manager creates receives. */\nexport type SharedControllerOptions = Omit<ControllerOptions, 'tour' | 'renderer'>\n\nexport interface DocentOptions {\n /** Tours to manage: an array, or a source that loads (and may live-update) them. */\n tours?: Tour[] | TourSource\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n /** Hooks per tour, keyed by tour id. */\n hooks?: Record<string, TourHooks>\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n environment: DocentEnvironment\n /** Builds a platform controller for a tour. The DOM package supplies this. */\n createController: (tour: Tour, options: SharedControllerOptions) => TourController\n now?: () => number\n /**\n * Start watching triggers right away. Default true. Framework bindings pass\n * `false` and call `connect()` / `disconnect()` from their mount lifecycle,\n * which keeps construction free of side effects (React StrictMode).\n */\n connect?: boolean\n}\n\nexport interface DocentState {\n /** Id of the tour currently running or paused, if any. */\n active: string | null\n /** Tours known to the manager. */\n tours: string[]\n}\n\nexport type DocentListener = (state: DocentState) => void\n\nexport interface StartOptions {\n /** Step id or index to start from. */\n at?: number | string\n}\n\ntype Cleanup = () => void\n\nfunction isTourSource(value: Tour[] | TourSource | undefined): value is TourSource {\n return !!value && !Array.isArray(value) && typeof (value as TourSource).load === 'function'\n}\n\nexport class Docent {\n private readonly options: DocentOptions\n private readonly env: DocentEnvironment\n private readonly baseStorage: StorageAdapter\n private identity: Identity\n private store: ProgressStore\n private tours = new Map<string, Tour>()\n private records = new Map<string, TourRecord | null>()\n private controller: TourController | undefined\n private activeId: string | null = null\n /** Tours whose trigger fired while another tour was running. */\n private queue: string[] = []\n private triggerCleanups: Cleanup[] = []\n /** `auto` triggers fire once per manager instance (per page load), not on every re-arm. */\n private readonly autoFired = new Set<string>()\n private readonly cleanups: Cleanup[] = []\n private readonly timers = new Set<ReturnType<typeof setTimeout>>()\n private readonly listeners = new Set<DocentListener>()\n private readonly eventListeners = new Set<(event: DocentEvent) => void>()\n private destroyed = false\n private loading: Promise<void> | undefined\n /** Wanted by the owner (between connect and disconnect). */\n private connected = false\n /** Listening to routes, the source and triggers. */\n private attached = false\n\n constructor(options: DocentOptions) {\n this.options = options\n this.env = options.environment\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.baseStorage = options.storage ?? createMemoryStorage()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, this.identity.id))\n if (options.connect !== false) this.connect()\n }\n\n /** Resolves once tours and progress are loaded. Loading starts on first use. */\n get ready(): Promise<void> {\n this.loading ??= this.load()\n return this.loading\n }\n\n /** Start watching routes, the tour source and triggers. Idempotent. */\n connect(): void {\n if (this.destroyed || this.connected) return\n this.connected = true\n void this.ready.then(() => {\n if (this.connected && !this.attached && !this.destroyed) this.attach()\n })\n }\n\n /**\n * Stop watching and remove any running tour without recording an outcome.\n * `connect()` resumes. Unlike `destroy()`, the manager stays usable.\n */\n async disconnect(): Promise<void> {\n this.connected = false\n if (this.attached) {\n this.attached = false\n this.disarmTriggers()\n for (const c of this.cleanups) c()\n this.cleanups.length = 0\n }\n this.queue = []\n await this.stopActive()\n this.emitState()\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** The tours currently managed. */\n getTours(): Tour[] {\n return [...this.tours.values()]\n }\n\n /**\n * Observe every lifecycle event from every tour, in addition to the `sink`\n * option. Returns an unsubscribe function. Used by devtools.\n */\n onEvent(listener: (event: DocentEvent) => void): () => void {\n this.eventListeners.add(listener)\n return () => this.eventListeners.delete(listener)\n }\n\n getState(): DocentState {\n return { active: this.activeId, tours: [...this.tours.keys()] }\n }\n\n subscribe(listener: DocentListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** The controller of the running tour, for fine-grained control. */\n get activeController(): TourController | undefined {\n return this.controller\n }\n\n /**\n * Set who the user is. Progress is stored per user id, and traits feed\n * `trait` conditions. Re-evaluates triggers, since the user may now qualify.\n */\n async identify(id: string | undefined, traits: Record<string, TraitValue> = {}): Promise<void> {\n await this.ready\n const userChanged = id !== this.identity.id\n this.identity = id === undefined ? { traits } : { id, traits }\n if (userChanged) {\n this.autoFired.clear()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, id))\n await this.loadRecords()\n }\n this.armTriggers()\n }\n\n /**\n * Report something that happened in your app. Starts tours with a matching\n * `event` trigger and advances a running step waiting on that event.\n */\n track(eventName: string): void {\n this.controller?.notify(eventName)\n for (const tour of this.tours.values()) {\n const t = tour.trigger\n if (t?.type === 'event' && t.name === eventName) this.fire(tour.id)\n }\n }\n\n /**\n * Start a tour now, ignoring its trigger, conditions and frequency. Use for\n * \"take the tour\" buttons. Stops any tour already running.\n */\n async start(tourId: string, options: StartOptions = {}): Promise<boolean> {\n await this.ready\n const tour = this.tours.get(tourId)\n if (!tour) return false\n await this.stopActive()\n await this.run(tour, options.at, true)\n return true\n }\n\n /** Whether a tour's conditions hold and its frequency allows showing it now. */\n isEligible(tourId: string): boolean {\n const tour = this.tours.get(tourId)\n if (!tour) return false\n return (\n shouldShow(tour, this.records.get(tourId) ?? null) &&\n evaluateAll(tour.conditions, this.getConditionEnv())\n )\n }\n\n /** Progress state of a tour for the current user. */\n tourState(tourId: string): TourProgressState {\n const tour = this.tours.get(tourId)\n const record = this.records.get(tourId)\n if (!record) return 'not-started'\n if (tour && record.version < tourVersion(tour)) return 'not-started'\n return record.state\n }\n\n /**\n * Replace a tour's definition, e.g. from a live editor. If it is running, the\n * current step re-renders with the new content. Triggers are not re-armed, so\n * editing never starts a tour by itself.\n */\n async updateTour(tour: Tour): Promise<void> {\n await this.ready\n this.tours.set(tour.id, tour)\n if (this.activeId === tour.id) await this.controller?.updateTour(tour)\n this.emitState()\n }\n\n /** Forget progress for one tour, or all of them, so they show again. */\n async reset(tourId?: string): Promise<void> {\n await this.ready\n const ids = tourId ? [tourId] : [...this.tours.keys()]\n for (const id of ids) {\n await this.store.clear(id)\n this.records.set(id, null)\n this.autoFired.delete(id)\n }\n this.armTriggers()\n }\n\n /**\n * Re-check triggers and tell the running tour the route may have changed.\n * Call after navigation if your router does not emit browser navigation events.\n */\n async refresh(): Promise<void> {\n await this.ready\n this.armTriggers()\n await this.controller?.routeChanged()\n }\n\n /** Stop the running tour (recorded as skipped). */\n async stop(): Promise<void> {\n await this.controller?.skip()\n }\n\n async destroy(): Promise<void> {\n await this.disconnect()\n this.destroyed = true\n this.listeners.clear()\n this.eventListeners.clear()\n }\n\n // -------------------------------------------------------------------------\n // Loading\n // -------------------------------------------------------------------------\n\n private async load(): Promise<void> {\n const source = this.options.tours\n const initial = isTourSource(source) ? await source.load() : (source ?? [])\n this.setTours(initial)\n await this.loadRecords()\n }\n\n private attach(): void {\n this.attached = true\n const source = this.options.tours\n if (isTourSource(source) && source.subscribe) {\n this.cleanups.push(\n source.subscribe((tours) => {\n this.setTours(tours)\n void this.loadRecords().then(() => this.armTriggers())\n }),\n )\n }\n if (this.env.onRouteChange) {\n this.cleanups.push(this.env.onRouteChange(() => this.armTriggers()))\n }\n this.armTriggers()\n }\n\n private setTours(tours: Tour[]): void {\n this.tours = new Map(tours.map((t) => [t.id, t]))\n this.queue = this.queue.filter((id) => this.tours.has(id))\n this.emitState()\n }\n\n private async loadRecords(): Promise<void> {\n const entries = await Promise.all(\n [...this.tours.keys()].map(async (id) => [id, await this.store.get(id)] as const),\n )\n this.records = new Map(entries)\n }\n\n // -------------------------------------------------------------------------\n // Triggers\n // -------------------------------------------------------------------------\n\n private disarmTriggers(): void {\n for (const c of this.triggerCleanups) c()\n this.triggerCleanups = []\n for (const t of this.timers) clearTimeout(t)\n this.timers.clear()\n }\n\n /**\n * (Re)arm every trigger. Cheap; called on load, identify, route change,\n * source updates and after a tour finishes (so chained tours can start).\n * `except` skips one tour, used for the tour that just finished.\n */\n private armTriggers(except?: string): void {\n if (this.destroyed || !this.attached) return\n this.disarmTriggers()\n for (const tour of this.tours.values()) {\n const trigger = tour.trigger\n if (!trigger || tour.id === except) continue\n this.arm(tour, trigger)\n }\n }\n\n private arm(tour: Tour, trigger: Trigger): void {\n switch (trigger.type) {\n case 'manual':\n case 'event':\n return\n case 'auto':\n // Marked as used in fire() only once it actually starts or queues, so a tour\n // that was not eligible at load can still start when the user qualifies.\n if (this.autoFired.has(tour.id)) return\n this.fireAfter(tour.id, trigger.delay)\n return\n case 'route': {\n const route = this.env.currentRoute?.()\n if (route !== undefined && matchRoute(trigger.pattern, route))\n this.fireAfter(tour.id, trigger.delay)\n return\n }\n case 'element': {\n if (!this.env.watchTarget) {\n if (this.env.hasTarget(trigger.target)) this.fireAfter(tour.id, trigger.delay)\n return\n }\n this.triggerCleanups.push(\n this.env.watchTarget(trigger.target, () => this.fireAfter(tour.id, trigger.delay)),\n )\n }\n }\n }\n\n private fireAfter(tourId: string, delay: number | undefined): void {\n if (!delay) {\n this.fire(tourId)\n return\n }\n const timer = setTimeout(() => {\n this.timers.delete(timer)\n this.fire(tourId)\n }, delay)\n this.timers.add(timer)\n }\n\n /** A trigger fired: start the tour if eligible, or queue it behind the running one. */\n private fire(tourId: string): void {\n if (this.destroyed || !this.attached || tourId === this.activeId) return\n if (!this.isEligible(tourId) || !this.triggerStillHolds(tourId)) return\n if (this.tours.get(tourId)?.trigger?.type === 'auto') this.autoFired.add(tourId)\n if (this.activeId) {\n if (!this.queue.includes(tourId)) this.queue.push(tourId)\n return\n }\n const tour = this.tours.get(tourId)\n if (tour) void this.run(tour, undefined, false)\n }\n\n /** Route triggers are only valid while the user is still on a matching route. */\n private triggerStillHolds(tourId: string): boolean {\n const trigger = this.tours.get(tourId)?.trigger\n if (trigger?.type !== 'route') return true\n const route = this.env.currentRoute?.()\n return route !== undefined && matchRoute(trigger.pattern, route)\n }\n\n // -------------------------------------------------------------------------\n // Running\n // -------------------------------------------------------------------------\n\n /** What conditions are evaluated against right now. Used by `isEligible` and devtools. */\n getConditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.env.hasTarget(t),\n tourState: (id) => this.tourState(id),\n custom: this.options.custom ?? {},\n }\n const route = this.env.currentRoute?.()\n if (route !== undefined) env.route = route\n return env\n }\n\n private sharedOptions(tour: Tour): SharedControllerOptions {\n const shared: SharedControllerOptions = {\n identity: this.identity,\n storage: scopeStorage(this.baseStorage, this.identity.id),\n tourState: (id) => this.tourState(id),\n }\n shared.sink = {\n emit: (event) => {\n this.options.sink?.emit(event)\n for (const l of this.eventListeners) l(event)\n },\n }\n const hooks = this.options.hooks?.[tour.id]\n if (hooks) shared.hooks = hooks\n if (this.options.custom) shared.custom = this.options.custom\n if (this.options.now) shared.now = this.options.now\n return shared\n }\n\n private async run(tour: Tour, at: number | string | undefined, manual: boolean): Promise<void> {\n this.queue = this.queue.filter((id) => id !== tour.id)\n const controller = this.options.createController(tour, this.sharedOptions(tour))\n this.controller = controller\n this.activeId = tour.id\n // Mirror what the controller persists, so tourState() is right while it runs.\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state: 'in-progress',\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.emitState()\n\n const off = controller.subscribe((state) => {\n if (\n state.status === 'completed' ||\n state.status === 'skipped' ||\n state.status === 'aborted'\n ) {\n off()\n this.finished(controller, tour, state.status === 'completed' ? 'completed' : 'skipped')\n }\n })\n\n const resume = !manual && at === undefined && tour.options?.persist\n if (resume) await controller.resume()\n else await controller.start(at)\n\n // start() returned without running (nothing happened): release the slot.\n if (controller.getState().status === 'idle' && this.controller === controller) {\n off()\n this.release(controller)\n }\n }\n\n /**\n * The controller emits the final status before it finishes writing storage,\n * so record the outcome here directly instead of reading it back.\n */\n private finished(controller: TourController, tour: Tour, state: 'completed' | 'skipped'): void {\n if (this.controller !== controller) return\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state,\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.release(controller, tour.id)\n }\n\n private release(controller: TourController, finishedId?: string): void {\n this.controller = undefined\n this.activeId = null\n this.emitState()\n // Let the finished controller complete its own cleanup (hide, persist, events) first.\n setTimeout(() => {\n void controller.destroy()\n this.drainQueue()\n // Tours whose conditions depend on the one that just ended may now qualify.\n if (!this.activeId) this.armTriggers(finishedId)\n }, 0)\n }\n\n /** Stop the running tour without recording an outcome (used before a manual start). */\n private async stopActive(): Promise<void> {\n const controller = this.controller\n if (!controller) return\n this.controller = undefined\n this.activeId = null\n await controller.destroy()\n }\n\n private drainQueue(): void {\n while (this.queue.length > 0 && !this.activeId) {\n const next = this.queue.shift()\n if (next) this.fire(next)\n }\n }\n\n private emitState(): void {\n const state = this.getState()\n for (const l of this.listeners) l(state)\n }\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -45,19 +45,45 @@ type Alignment = 'start' | 'center' | 'end';
45
45
  * would overflow the viewport.
46
46
  */
47
47
  type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`;
48
+ /**
49
+ * What connects the popover to its target.
50
+ * - `caret` (default): a small notch on the popover's edge.
51
+ * - `none`: nothing.
52
+ * - Connectors, drawn from the popover to the target: `line`, `dashed`,
53
+ * `dotted`, `curve`, `curve-dashed`, `squiggle`, `loop`, `elbow`, `sketch`
54
+ * (hand-drawn double stroke) and `pin` (dotted line ending in a dot).
55
+ */
56
+ type ArrowStyle = 'caret' | 'none' | 'line' | 'dashed' | 'dotted' | 'curve' | 'curve-dashed' | 'squiggle' | 'loop' | 'elbow' | 'sketch' | 'pin';
57
+ /** Shape of the cutout around the target. `circle` circumscribes the target. */
58
+ type SpotlightShape = 'rounded' | 'rect' | 'pill' | 'circle';
59
+ /** Outline drawn around the cutout. `pulse` gently repeats to draw the eye. */
60
+ type SpotlightRing = 'hairline' | 'none' | 'glow' | 'pulse' | 'dashed' | 'solid';
61
+ /**
62
+ * How the rest of the page is treated.
63
+ * - `dim` (default): a tinted scrim.
64
+ * - `blur`: scrim plus a soft blur of the page.
65
+ * - `vignette`: clear near the target, darker toward the edges.
66
+ * - `none`: no scrim and the page stays usable (hint-style tours).
67
+ */
68
+ type OverlayStyle = 'dim' | 'blur' | 'vignette' | 'none';
48
69
  interface SpotlightOptions {
49
70
  /** Space between the target's edge and the cutout, in px. */
50
71
  padding?: number;
51
- /** Corner radius of the cutout, in px. */
72
+ /** Corner radius of the cutout, in px (for `rounded`). */
52
73
  radius?: number;
53
74
  /** Animate the cutout moving between targets. */
54
75
  animate?: boolean;
76
+ shape?: SpotlightShape;
77
+ ring?: SpotlightRing;
55
78
  }
56
79
  interface OverlayOptions {
80
+ style?: OverlayStyle;
57
81
  /** Backdrop colour, any CSS colour. */
58
82
  color?: string;
59
83
  /** Backdrop opacity, 0–1. */
60
84
  opacity?: number;
85
+ /** Blur radius for the `blur` style, in px. */
86
+ blur?: number;
61
87
  }
62
88
  interface ScrollOptions {
63
89
  /** Scroll the target into view before showing the step. */
@@ -174,6 +200,10 @@ type Frequency = 'once' | 'until-completed' | 'always';
174
200
  * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.
175
201
  */
176
202
  interface Theme {
203
+ /** Color of drawn connectors (arrow styles other than caret). */
204
+ connector?: string;
205
+ /** Color of the spotlight ring. */
206
+ ring?: string;
177
207
  background?: string;
178
208
  foreground?: string;
179
209
  muted?: string;
@@ -208,8 +238,12 @@ interface Step {
208
238
  format?: 'text' | 'markdown';
209
239
  media?: Media;
210
240
  placement?: Placement;
241
+ /** Per-step override of the tour's arrow style. */
242
+ arrow?: ArrowStyle;
211
243
  /** Per-step override of the tour's spotlight options. */
212
244
  spotlight?: SpotlightOptions;
245
+ /** Per-step override of the tour's overlay options. */
246
+ overlay?: OverlayOptions;
213
247
  advance?: Advance;
214
248
  interaction?: Interaction;
215
249
  /** Skip this step when the condition is false. */
@@ -233,6 +267,7 @@ interface TourOptions {
233
267
  allowClose?: boolean;
234
268
  closeOnOverlayClick?: boolean;
235
269
  keyboard?: boolean;
270
+ arrow?: ArrowStyle;
236
271
  spotlight?: SpotlightOptions;
237
272
  overlay?: OverlayOptions;
238
273
  scroll?: ScrollOptions;
@@ -468,7 +503,7 @@ interface ControllerOptions {
468
503
  }
469
504
  type StateListener = (state: EngineState) => void;
470
505
  export declare class TourController {
471
- readonly tour: Tour;
506
+ tour: Tour;
472
507
  private state;
473
508
  private readonly renderer;
474
509
  private readonly identity;
@@ -497,6 +532,11 @@ export declare class TourController {
497
532
  /** The user gave up on the tour (Skip button, close, Escape). */
498
533
  skip(): Promise<void>;
499
534
  abort(reason: string): Promise<void>;
535
+ /**
536
+ * Swap in a new definition of this tour (live editing). A running tour
537
+ * re-renders its current step, or the nearest one if that step was removed.
538
+ */
539
+ updateTour(tour: Tour): Promise<void>;
500
540
  /** Report a named application event. Advances a step waiting on it. */
501
541
  notify(eventName: string): void;
502
542
  /** Tell the controller the route changed. Pauses or resumes route-bound steps. */
@@ -693,6 +733,12 @@ export declare class Docent {
693
733
  isEligible(tourId: string): boolean;
694
734
  /** Progress state of a tour for the current user. */
695
735
  tourState(tourId: string): TourProgressState;
736
+ /**
737
+ * Replace a tour's definition, e.g. from a live editor. If it is running, the
738
+ * current step re-renders with the new content. Triggers are not re-armed, so
739
+ * editing never starts a tour by itself.
740
+ */
741
+ updateTour(tour: Tour): Promise<void>;
696
742
  /** Forget progress for one tour, or all of them, so they show again. */
697
743
  reset(tourId?: string): Promise<void>;
698
744
  /**
@@ -744,5 +790,5 @@ export declare class Docent {
744
790
  */
745
791
  export declare function scopeStorage(base: StorageAdapter, userId: string | undefined): StorageAdapter;
746
792
  //#endregion
747
- export type { Advance, Alignment, Condition, ConditionEnv, ControllerOptions, CustomPredicate, DocentEnvironment, DocentEvent, DocentEventType, DocentListener, DocentOptions, DocentState, EngineAction, EngineContext, EngineState, EventInput, EventSink, Frequency, Identity, Interaction, Labels, MaybePromise, Media, OnMissing, OverlayOptions, Placement, Progress, RenderActions, RenderContext, Renderer, SchemaVersion, ScrollOptions, SharedControllerOptions, Side, SpotlightOptions, StartOptions, StateListener, Step, StepButtons, StepContext, StepHooks, StorageAdapter, Target, TargetSpec, Theme, Tour, TourHooks, TourOptions, TourProgressState, TourRecord, TourSource, TourStatus, TraitOperator, TraitValue, Trigger };
793
+ export type { Advance, Alignment, ArrowStyle, Condition, ConditionEnv, ControllerOptions, CustomPredicate, DocentEnvironment, DocentEvent, DocentEventType, DocentListener, DocentOptions, DocentState, EngineAction, EngineContext, EngineState, EventInput, EventSink, Frequency, Identity, Interaction, Labels, MaybePromise, Media, OnMissing, OverlayOptions, OverlayStyle, Placement, Progress, RenderActions, RenderContext, Renderer, SchemaVersion, ScrollOptions, SharedControllerOptions, Side, SpotlightOptions, SpotlightRing, SpotlightShape, StartOptions, StateListener, Step, StepButtons, StepContext, StepHooks, StorageAdapter, Target, TargetSpec, Theme, Tour, TourHooks, TourOptions, TourProgressState, TourRecord, TourSource, TourStatus, TraitOperator, TraitValue, Trigger };
748
794
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -45,19 +45,45 @@ type Alignment = 'start' | 'center' | 'end';
45
45
  * would overflow the viewport.
46
46
  */
47
47
  type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`;
48
+ /**
49
+ * What connects the popover to its target.
50
+ * - `caret` (default): a small notch on the popover's edge.
51
+ * - `none`: nothing.
52
+ * - Connectors, drawn from the popover to the target: `line`, `dashed`,
53
+ * `dotted`, `curve`, `curve-dashed`, `squiggle`, `loop`, `elbow`, `sketch`
54
+ * (hand-drawn double stroke) and `pin` (dotted line ending in a dot).
55
+ */
56
+ type ArrowStyle = 'caret' | 'none' | 'line' | 'dashed' | 'dotted' | 'curve' | 'curve-dashed' | 'squiggle' | 'loop' | 'elbow' | 'sketch' | 'pin';
57
+ /** Shape of the cutout around the target. `circle` circumscribes the target. */
58
+ type SpotlightShape = 'rounded' | 'rect' | 'pill' | 'circle';
59
+ /** Outline drawn around the cutout. `pulse` gently repeats to draw the eye. */
60
+ type SpotlightRing = 'hairline' | 'none' | 'glow' | 'pulse' | 'dashed' | 'solid';
61
+ /**
62
+ * How the rest of the page is treated.
63
+ * - `dim` (default): a tinted scrim.
64
+ * - `blur`: scrim plus a soft blur of the page.
65
+ * - `vignette`: clear near the target, darker toward the edges.
66
+ * - `none`: no scrim and the page stays usable (hint-style tours).
67
+ */
68
+ type OverlayStyle = 'dim' | 'blur' | 'vignette' | 'none';
48
69
  interface SpotlightOptions {
49
70
  /** Space between the target's edge and the cutout, in px. */
50
71
  padding?: number;
51
- /** Corner radius of the cutout, in px. */
72
+ /** Corner radius of the cutout, in px (for `rounded`). */
52
73
  radius?: number;
53
74
  /** Animate the cutout moving between targets. */
54
75
  animate?: boolean;
76
+ shape?: SpotlightShape;
77
+ ring?: SpotlightRing;
55
78
  }
56
79
  interface OverlayOptions {
80
+ style?: OverlayStyle;
57
81
  /** Backdrop colour, any CSS colour. */
58
82
  color?: string;
59
83
  /** Backdrop opacity, 0–1. */
60
84
  opacity?: number;
85
+ /** Blur radius for the `blur` style, in px. */
86
+ blur?: number;
61
87
  }
62
88
  interface ScrollOptions {
63
89
  /** Scroll the target into view before showing the step. */
@@ -174,6 +200,10 @@ type Frequency = 'once' | 'until-completed' | 'always';
174
200
  * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.
175
201
  */
176
202
  interface Theme {
203
+ /** Color of drawn connectors (arrow styles other than caret). */
204
+ connector?: string;
205
+ /** Color of the spotlight ring. */
206
+ ring?: string;
177
207
  background?: string;
178
208
  foreground?: string;
179
209
  muted?: string;
@@ -208,8 +238,12 @@ interface Step {
208
238
  format?: 'text' | 'markdown';
209
239
  media?: Media;
210
240
  placement?: Placement;
241
+ /** Per-step override of the tour's arrow style. */
242
+ arrow?: ArrowStyle;
211
243
  /** Per-step override of the tour's spotlight options. */
212
244
  spotlight?: SpotlightOptions;
245
+ /** Per-step override of the tour's overlay options. */
246
+ overlay?: OverlayOptions;
213
247
  advance?: Advance;
214
248
  interaction?: Interaction;
215
249
  /** Skip this step when the condition is false. */
@@ -233,6 +267,7 @@ interface TourOptions {
233
267
  allowClose?: boolean;
234
268
  closeOnOverlayClick?: boolean;
235
269
  keyboard?: boolean;
270
+ arrow?: ArrowStyle;
236
271
  spotlight?: SpotlightOptions;
237
272
  overlay?: OverlayOptions;
238
273
  scroll?: ScrollOptions;
@@ -468,7 +503,7 @@ interface ControllerOptions {
468
503
  }
469
504
  type StateListener = (state: EngineState) => void;
470
505
  export declare class TourController {
471
- readonly tour: Tour;
506
+ tour: Tour;
472
507
  private state;
473
508
  private readonly renderer;
474
509
  private readonly identity;
@@ -497,6 +532,11 @@ export declare class TourController {
497
532
  /** The user gave up on the tour (Skip button, close, Escape). */
498
533
  skip(): Promise<void>;
499
534
  abort(reason: string): Promise<void>;
535
+ /**
536
+ * Swap in a new definition of this tour (live editing). A running tour
537
+ * re-renders its current step, or the nearest one if that step was removed.
538
+ */
539
+ updateTour(tour: Tour): Promise<void>;
500
540
  /** Report a named application event. Advances a step waiting on it. */
501
541
  notify(eventName: string): void;
502
542
  /** Tell the controller the route changed. Pauses or resumes route-bound steps. */
@@ -693,6 +733,12 @@ export declare class Docent {
693
733
  isEligible(tourId: string): boolean;
694
734
  /** Progress state of a tour for the current user. */
695
735
  tourState(tourId: string): TourProgressState;
736
+ /**
737
+ * Replace a tour's definition, e.g. from a live editor. If it is running, the
738
+ * current step re-renders with the new content. Triggers are not re-armed, so
739
+ * editing never starts a tour by itself.
740
+ */
741
+ updateTour(tour: Tour): Promise<void>;
696
742
  /** Forget progress for one tour, or all of them, so they show again. */
697
743
  reset(tourId?: string): Promise<void>;
698
744
  /**
@@ -744,5 +790,5 @@ export declare class Docent {
744
790
  */
745
791
  export declare function scopeStorage(base: StorageAdapter, userId: string | undefined): StorageAdapter;
746
792
  //#endregion
747
- export type { Advance, Alignment, Condition, ConditionEnv, ControllerOptions, CustomPredicate, DocentEnvironment, DocentEvent, DocentEventType, DocentListener, DocentOptions, DocentState, EngineAction, EngineContext, EngineState, EventInput, EventSink, Frequency, Identity, Interaction, Labels, MaybePromise, Media, OnMissing, OverlayOptions, Placement, Progress, RenderActions, RenderContext, Renderer, SchemaVersion, ScrollOptions, SharedControllerOptions, Side, SpotlightOptions, StartOptions, StateListener, Step, StepButtons, StepContext, StepHooks, StorageAdapter, Target, TargetSpec, Theme, Tour, TourHooks, TourOptions, TourProgressState, TourRecord, TourSource, TourStatus, TraitOperator, TraitValue, Trigger };
793
+ export type { Advance, Alignment, ArrowStyle, Condition, ConditionEnv, ControllerOptions, CustomPredicate, DocentEnvironment, DocentEvent, DocentEventType, DocentListener, DocentOptions, DocentState, EngineAction, EngineContext, EngineState, EventInput, EventSink, Frequency, Identity, Interaction, Labels, MaybePromise, Media, OnMissing, OverlayOptions, OverlayStyle, Placement, Progress, RenderActions, RenderContext, Renderer, SchemaVersion, ScrollOptions, SharedControllerOptions, Side, SpotlightOptions, SpotlightRing, SpotlightShape, StartOptions, StateListener, Step, StepButtons, StepContext, StepHooks, StorageAdapter, Target, TargetSpec, Theme, Tour, TourHooks, TourOptions, TourProgressState, TourRecord, TourSource, TourStatus, TraitOperator, TraitValue, Trigger };
748
794
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -441,6 +441,28 @@ var TourController = class {
441
441
  });
442
442
  await this.finish();
443
443
  }
444
+ /**
445
+ * Swap in a new definition of this tour (live editing). A running tour
446
+ * re-renders its current step, or the nearest one if that step was removed.
447
+ */
448
+ async updateTour(tour) {
449
+ const currentId = this.currentStep()?.id;
450
+ this.tour = tour;
451
+ if (!this.isActive()) return;
452
+ let index = currentId === void 0 ? -1 : tour.steps.findIndex((s) => s.id === currentId);
453
+ if (index === -1) index = Math.min(this.state.index, tour.steps.length - 1);
454
+ if (index < 0) {
455
+ await this.abort("tour-emptied");
456
+ return;
457
+ }
458
+ const history = this.state.history.filter((i) => i < tour.steps.length && i !== index);
459
+ this.setState({
460
+ ...this.state,
461
+ index,
462
+ history
463
+ });
464
+ if (this.state.status === "running") await this.showCurrent();
465
+ }
444
466
  /** Report a named application event. Advances a step waiting on it. */
445
467
  notify(eventName) {
446
468
  const advance = this.currentStep()?.advance;
@@ -834,6 +856,17 @@ var Docent = class {
834
856
  if (tour && record.version < tourVersion(tour)) return "not-started";
835
857
  return record.state;
836
858
  }
859
+ /**
860
+ * Replace a tour's definition, e.g. from a live editor. If it is running, the
861
+ * current step re-renders with the new content. Triggers are not re-armed, so
862
+ * editing never starts a tour by itself.
863
+ */
864
+ async updateTour(tour) {
865
+ await this.ready;
866
+ this.tours.set(tour.id, tour);
867
+ if (this.activeId === tour.id) await this.controller?.updateTour(tour);
868
+ this.emitState();
869
+ }
837
870
  /** Forget progress for one tour, or all of them, so they show again. */
838
871
  async reset(tourId) {
839
872
  await this.ready;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/schema/tour.ts","../src/define.ts","../src/engine/route.ts","../src/engine/conditions.ts","../src/seams.ts","../src/engine/progress.ts","../src/engine/events.ts","../src/engine/reducer.ts","../src/engine/controller.ts","../src/manager/scoped-storage.ts","../src/manager/docent.ts"],"sourcesContent":["/**\n * Tour schema — the JSON contract shared by the core engine, every renderer,\n * the visual builder and the hosted service.\n *\n * Everything in this file must stay JSON-serialisable. Functions (hooks) live\n * in `hooks.ts` and are attached at runtime, keyed by step id.\n */\n\n/** Current schema version. Bump only on breaking changes to this file. */\nexport const SCHEMA_VERSION = 1 as const\n\nexport type SchemaVersion = typeof SCHEMA_VERSION\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\n/**\n * Where a step points.\n *\n * - A bare string is a CSS selector (web only). Convenient for hand-written tours.\n * - A {@link TargetSpec} is the portable, self-healing form the builder produces.\n *\n * Prefer `{ name }` over raw selectors: on the web it resolves to\n * `[data-docent=\"<name>\"]`, on native to a `testID`, so one tour works everywhere.\n */\nexport type Target = string | TargetSpec\n\nexport interface TargetSpec {\n /**\n * Logical name. Web: `[data-docent=\"<name>\"]`. Native: `testID` / `nativeID`.\n * The most robust anchor; survives refactors and works cross-platform.\n */\n name?: string\n /**\n * Ordered CSS selector fallbacks (web only). Tried in order until one matches.\n * The builder fills several so a tour keeps working after markup changes.\n */\n selectors?: string[]\n /** Explicit native identifier when it differs from `name`. */\n native?: string\n /** Restrict the search to a container matched by this selector. */\n within?: string\n /** When a selector matches several elements, pick this index (default 0). */\n nth?: number\n}\n\n// ---------------------------------------------------------------------------\n// Placement & visuals\n// ---------------------------------------------------------------------------\n\nexport type Side = 'top' | 'right' | 'bottom' | 'left'\nexport type Alignment = 'start' | 'center' | 'end'\n\n/**\n * Preferred popover position relative to the target. `auto` lets the renderer\n * pick the side with the most room. Any placement flips or shifts when it\n * would overflow the viewport.\n */\nexport type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`\n\nexport interface SpotlightOptions {\n /** Space between the target's edge and the cutout, in px. */\n padding?: number\n /** Corner radius of the cutout, in px. */\n radius?: number\n /** Animate the cutout moving between targets. */\n animate?: boolean\n}\n\nexport interface OverlayOptions {\n /** Backdrop colour, any CSS colour. */\n color?: string\n /** Backdrop opacity, 0–1. */\n opacity?: number\n}\n\nexport interface ScrollOptions {\n /** Scroll the target into view before showing the step. */\n enabled?: boolean\n behavior?: 'auto' | 'smooth'\n block?: 'start' | 'center' | 'end' | 'nearest'\n}\n\nexport interface Media {\n type: 'image' | 'video'\n src: string\n alt?: string\n}\n\n// ---------------------------------------------------------------------------\n// Behaviour\n// ---------------------------------------------------------------------------\n\n/**\n * How a step completes.\n *\n * - `'button'` (default): the user presses Next.\n * - `click` / `input`: the user interacts with the target (or another element).\n * - `event`: a named event is emitted through the runtime.\n * - `element`: an element appears (e.g. a menu the user was asked to open).\n * - `delay`: automatically after `ms`.\n */\nexport type Advance =\n | 'button'\n | { on: 'click'; target?: Target }\n | { on: 'input'; target?: Target; match?: string }\n | { on: 'event'; name: string }\n | { on: 'element'; target: Target }\n | { on: 'delay'; ms: number }\n\n/** Whether the user can interact with the spotlighted target. */\nexport type Interaction = 'block' | 'allow'\n\n/** What to do when a step's target cannot be found. */\nexport type OnMissing = 'skip' | 'wait' | 'abort'\n\nexport interface StepButtons {\n back?: boolean\n next?: boolean\n skip?: boolean\n close?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Conditions & triggers\n// ---------------------------------------------------------------------------\n\nexport type TraitValue = string | number | boolean | null | string[]\n\nexport type TraitOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'in'\n | 'nin'\n | 'contains'\n | 'exists'\n | 'missing'\n\n/**\n * Serialisable predicate evaluated by the core at runtime.\n * `custom` predicates are registered by name on the runtime.\n */\nexport type Condition =\n | { type: 'trait'; key: string; op: TraitOperator; value?: TraitValue }\n | { type: 'route'; pattern: string }\n | { type: 'element'; target: Target; exists?: boolean }\n | { type: 'tour'; id: string; state: TourProgressState }\n | { type: 'all'; conditions: Condition[] }\n | { type: 'any'; conditions: Condition[] }\n | { type: 'not'; condition: Condition }\n | { type: 'custom'; name: string; args?: Record<string, TraitValue> }\n\nexport type TourProgressState = 'not-started' | 'in-progress' | 'completed' | 'skipped'\n\n/** What starts a tour. `manual` means only through the runtime API. */\nexport type Trigger =\n | { type: 'manual' }\n | { type: 'auto'; delay?: number }\n | { type: 'route'; pattern: string; delay?: number }\n | { type: 'element'; target: Target; delay?: number }\n | { type: 'event'; name: string }\n\n/**\n * How often an eligible user sees the tour.\n * - `once`: show once per {@link Tour.version}, however it ended.\n * - `until-completed`: keep offering it until the user finishes it.\n * - `always`: every time the trigger fires.\n */\nexport type Frequency = 'once' | 'until-completed' | 'always'\n\n// ---------------------------------------------------------------------------\n// Theme\n// ---------------------------------------------------------------------------\n\n/**\n * Visual tokens, serialisable so a builder or customizer can produce them.\n * Each maps to a CSS custom property in the renderer (`--docent-*`).\n * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.\n */\nexport interface Theme {\n background?: string\n foreground?: string\n muted?: string\n accent?: string\n accentForeground?: string\n radius?: string\n shadow?: string\n font?: string\n width?: string\n overlay?: string\n overlayOpacity?: string\n duration?: string\n zIndex?: string\n}\n\n// ---------------------------------------------------------------------------\n// Labels\n// ---------------------------------------------------------------------------\n\nexport interface Labels {\n next?: string\n back?: string\n skip?: string\n done?: string\n close?: string\n /** Supports `{current}` and `{total}` placeholders. */\n progress?: string\n}\n\n// ---------------------------------------------------------------------------\n// Step\n// ---------------------------------------------------------------------------\n\nexport interface Step {\n /** Unique within the tour. Used for persistence, hooks and analytics. */\n id: string\n /** Omit for a centred modal step (welcome / finish screens). */\n target?: Target\n title?: string\n body?: string\n /** How `body` is interpreted. Renderers never inject raw HTML. */\n format?: 'text' | 'markdown'\n media?: Media\n placement?: Placement\n /** Per-step override of the tour's spotlight options. */\n spotlight?: SpotlightOptions\n advance?: Advance\n interaction?: Interaction\n /** Skip this step when the condition is false. */\n condition?: Condition\n onMissing?: OnMissing\n /** How long to wait for the target when `onMissing` is `wait`, in ms. */\n waitFor?: number\n /** URL pattern this step belongs to. Enables multi-page tours. */\n route?: string\n buttons?: StepButtons\n scroll?: ScrollOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Tour\n// ---------------------------------------------------------------------------\n\nexport interface TourOptions {\n /** Persist progress so the tour survives navigation and reloads. */\n persist?: boolean\n frequency?: Frequency\n showProgress?: boolean\n /** Allow closing with Escape or the close button. */\n allowClose?: boolean\n closeOnOverlayClick?: boolean\n keyboard?: boolean\n spotlight?: SpotlightOptions\n overlay?: OverlayOptions\n scroll?: ScrollOptions\n labels?: Labels\n /** Visual tokens applied on top of the renderer's theme. */\n theme?: Theme\n /** Name of a template registered on the renderer (slots, css, theme). */\n template?: string\n}\n\nexport interface Tour {\n schemaVersion: SchemaVersion\n /** Stable identifier. Used for persistence, targeting and analytics. */\n id: string\n /** Bump to re-show the tour to users who already saw an older version. */\n version?: number\n /** Human-readable name, mainly for the builder and dashboards. */\n name?: string\n description?: string\n steps: Step[]\n trigger?: Trigger\n /** All must hold for the tour to be eligible. */\n conditions?: Condition[]\n options?: TourOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n","import { SCHEMA_VERSION, type SchemaVersion, type Tour } from './schema/tour'\n\n/**\n * Identity helper that gives hand-written tours full type inference and\n * fills in the schema version. Returns the same object.\n */\nexport function defineTour(\n tour: Omit<Tour, 'schemaVersion'> & { schemaVersion?: SchemaVersion },\n): Tour {\n return { ...tour, schemaVersion: SCHEMA_VERSION }\n}\n","/**\n * Route pattern matching for `route` triggers, conditions and step routes.\n *\n * Patterns are path globs:\n * - `/settings` exact\n * - `/users/:id` one segment (named for readability, value ignored)\n * - `/users/*` one segment\n * - `/docs/**` zero or more segments\n *\n * Query strings and hashes are ignored. Trailing slashes are tolerated.\n */\n\nfunction segments(path: string): string[] {\n const clean = path.split(/[?#]/, 1)[0] ?? ''\n return clean.split('/').filter(Boolean)\n}\n\nfunction matchSegments(pattern: string[], path: string[], pi = 0, si = 0): boolean {\n if (pi === pattern.length) return si === path.length\n const p = pattern[pi]\n if (p === '**') {\n for (let k = si; k <= path.length; k++) {\n if (matchSegments(pattern, path, pi + 1, k)) return true\n }\n return false\n }\n if (si === path.length) return false\n if (p === '*' || p?.startsWith(':') || p === path[si]) {\n return matchSegments(pattern, path, pi + 1, si + 1)\n }\n return false\n}\n\nexport function matchRoute(pattern: string, path: string): boolean {\n return matchSegments(segments(pattern), segments(path))\n}\n","/**\n * Evaluates serialisable {@link Condition}s against a runtime environment.\n * Pure: everything it needs is passed in.\n */\n\nimport type { Condition, Target, TourProgressState, TraitValue } from '../schema/tour'\nimport type { Identity } from '../seams'\nimport { matchRoute } from './route'\n\nexport type CustomPredicate = (args?: Record<string, TraitValue>) => boolean\n\nexport interface ConditionEnv {\n identity: Identity\n /** Current path, e.g. `/invoices/new`. */\n route?: string\n elementExists?: (target: Target) => boolean\n tourState?: (tourId: string) => TourProgressState\n custom?: Record<string, CustomPredicate>\n}\n\nfunction sameValue(a: TraitValue | undefined, b: TraitValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => v === b[i])\n }\n return a === b\n}\n\nfunction compare(a: TraitValue | undefined, b: TraitValue | undefined): number | null {\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n return null\n}\n\nfunction contains(haystack: TraitValue | undefined, needle: TraitValue | undefined): boolean {\n if (Array.isArray(haystack)) return typeof needle === 'string' && haystack.includes(needle)\n if (typeof haystack === 'string') {\n return (\n (typeof needle === 'string' || typeof needle === 'number') &&\n haystack.includes(String(needle))\n )\n }\n return false\n}\n\nfunction inList(value: TraitValue | undefined, list: TraitValue | undefined): boolean {\n if (!Array.isArray(list)) return false\n if (Array.isArray(value)) return value.some((v) => list.includes(v))\n return typeof value === 'string' && list.includes(value)\n}\n\nexport function evaluateTrait(\n actual: TraitValue | undefined,\n op: Extract<Condition, { type: 'trait' }>['op'],\n expected: TraitValue | undefined,\n): boolean {\n switch (op) {\n case 'exists':\n return actual !== undefined && actual !== null\n case 'missing':\n return actual === undefined || actual === null\n case 'eq':\n return sameValue(actual, expected)\n case 'neq':\n return !sameValue(actual, expected)\n case 'in':\n return inList(actual, expected)\n case 'nin':\n return !inList(actual, expected)\n case 'contains':\n return contains(actual, expected)\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const c = compare(actual, expected)\n if (c === null) return false\n if (op === 'gt') return c > 0\n if (op === 'gte') return c >= 0\n if (op === 'lt') return c < 0\n return c <= 0\n }\n }\n}\n\nexport function evaluateCondition(condition: Condition, env: ConditionEnv): boolean {\n switch (condition.type) {\n case 'trait':\n return evaluateTrait(env.identity.traits[condition.key], condition.op, condition.value)\n case 'route':\n return env.route !== undefined && matchRoute(condition.pattern, env.route)\n case 'element': {\n const exists = env.elementExists?.(condition.target) ?? false\n return exists === (condition.exists ?? true)\n }\n case 'tour':\n return (env.tourState?.(condition.id) ?? 'not-started') === condition.state\n case 'all':\n return condition.conditions.every((c) => evaluateCondition(c, env))\n case 'any':\n return condition.conditions.some((c) => evaluateCondition(c, env))\n case 'not':\n return !evaluateCondition(condition.condition, env)\n case 'custom':\n return env.custom?.[condition.name]?.(condition.args) ?? false\n }\n}\n\n/** All conditions must hold. An empty or missing list holds. */\nexport function evaluateAll(conditions: Condition[] | undefined, env: ConditionEnv): boolean {\n return (conditions ?? []).every((c) => evaluateCondition(c, env))\n}\n","/**\n * Extension seams — the interfaces the runtime is built against so that tours,\n * user identity, storage and analytics can come from anywhere: inline JSON,\n * a local file, the user's own backend, or a hosted service.\n */\n\nimport type { Tour, TraitValue } from './schema/tour'\n\nexport type MaybePromise<T> = T | Promise<T>\n\n// ---------------------------------------------------------------------------\n// Tour source\n// ---------------------------------------------------------------------------\n\nexport interface TourSource {\n /** Return every tour this source knows about. */\n load(): MaybePromise<Tour[]>\n /**\n * Optional live updates. Return an unsubscribe function.\n * Lets a hosted source push tour changes without a page reload.\n */\n subscribe?(listener: (tours: Tour[]) => void): () => void\n}\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\nexport interface Identity {\n /** Stable user id. Omit for anonymous visitors. */\n id?: string\n /** Attributes that `trait` conditions evaluate against. */\n traits: Record<string, TraitValue>\n}\n\nexport const ANONYMOUS_IDENTITY: Identity = { traits: {} }\n\n// ---------------------------------------------------------------------------\n// Storage\n// ---------------------------------------------------------------------------\n\n/**\n * Key/value storage for progress and seen-state. Shape matches `localStorage`\n * but every method may be async so React Native's AsyncStorage fits too.\n */\nexport interface StorageAdapter {\n get(key: string): MaybePromise<string | null>\n set(key: string, value: string): MaybePromise<void>\n remove(key: string): MaybePromise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DocentEventType =\n | 'tour:started'\n | 'tour:completed'\n | 'tour:skipped'\n | 'tour:aborted'\n | 'step:shown'\n | 'step:completed'\n | 'step:skipped'\n | 'step:missing'\n\nexport interface DocentEvent {\n type: DocentEventType\n tourId: string\n tourVersion: number\n stepId?: string\n stepIndex?: number\n /** Unix epoch milliseconds. */\n timestamp: number\n identity: Identity\n}\n\n/** Receives every lifecycle event. Wire it to console, your analytics, or a hosted endpoint. */\nexport interface EventSink {\n emit(event: DocentEvent): void\n}\n","/**\n * Persisted per-tour progress: what the user has seen, where they stopped,\n * and whether the tour should be offered again.\n */\n\nimport type { Frequency, Tour, TourProgressState } from '../schema/tour'\nimport type { StorageAdapter } from '../seams'\n\nexport interface TourRecord {\n tourId: string\n version: number\n state: TourProgressState\n /** Step to resume from when the tour was interrupted. */\n stepId?: string\n /** Unix epoch milliseconds. */\n updatedAt: number\n}\n\nexport const STORAGE_PREFIX = 'docent:'\n\nexport function storageKey(tourId: string): string {\n return `${STORAGE_PREFIX}${tourId}`\n}\n\nexport function tourVersion(tour: Tour): number {\n return tour.version ?? 1\n}\n\n/** Whether a tour should be offered given what the user has already done. */\nexport function shouldShow(tour: Tour, record: TourRecord | null): boolean {\n if (!record) return true\n if (record.version < tourVersion(tour)) return true\n const frequency: Frequency = tour.options?.frequency ?? 'once'\n switch (frequency) {\n case 'always':\n return true\n case 'until-completed':\n return record.state !== 'completed'\n case 'once':\n return record.state === 'not-started' || record.state === 'in-progress'\n }\n}\n\nexport class ProgressStore {\n constructor(private readonly storage: StorageAdapter) {}\n\n async get(tourId: string): Promise<TourRecord | null> {\n const raw = await this.storage.get(storageKey(tourId))\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<TourRecord>\n if (parsed.tourId !== tourId || typeof parsed.version !== 'number' || !parsed.state) {\n return null\n }\n return parsed as TourRecord\n } catch {\n return null\n }\n }\n\n async set(record: TourRecord): Promise<void> {\n await this.storage.set(storageKey(record.tourId), JSON.stringify(record))\n }\n\n async clear(tourId: string): Promise<void> {\n await this.storage.remove(storageKey(tourId))\n }\n}\n\n/** In-memory adapter. Default when no storage is configured, and handy in tests. */\nexport function createMemoryStorage(): StorageAdapter {\n const map = new Map<string, string>()\n return {\n get: (key) => map.get(key) ?? null,\n set: (key, value) => {\n map.set(key, value)\n },\n remove: (key) => {\n map.delete(key)\n },\n }\n}\n","import type { Tour } from '../schema/tour'\nimport type { DocentEvent, DocentEventType, EventSink, Identity } from '../seams'\nimport { tourVersion } from './progress'\n\nexport interface EventInput {\n tour: Tour\n identity: Identity\n stepIndex?: number\n now?: () => number\n}\n\nexport function createEvent(type: DocentEventType, input: EventInput): DocentEvent {\n const event: DocentEvent = {\n type,\n tourId: input.tour.id,\n tourVersion: tourVersion(input.tour),\n timestamp: (input.now ?? Date.now)(),\n identity: input.identity,\n }\n if (input.stepIndex !== undefined && input.stepIndex >= 0) {\n const step = input.tour.steps[input.stepIndex]\n event.stepIndex = input.stepIndex\n if (step) event.stepId = step.id\n }\n return event\n}\n\n/** Sink that drops everything. Default when none is configured. */\nexport const NOOP_SINK: EventSink = { emit() {} }\n\n/** Fan out to several sinks. */\nexport function combineSinks(...sinks: EventSink[]): EventSink {\n return {\n emit: (e) => {\n for (const s of sinks) s.emit(e)\n },\n }\n}\n","/**\n * Pure state machine for a single running tour. No side effects, no timers,\n * no DOM. Eligibility of steps (conditions, missing targets) is supplied by\n * the caller through {@link EngineContext} so the reducer stays pure.\n */\n\nimport type { Tour } from '../schema/tour'\n\nexport type TourStatus = 'idle' | 'running' | 'paused' | 'completed' | 'skipped' | 'aborted'\n\nexport interface EngineState {\n status: TourStatus\n /** Index into `tour.steps`, or -1 when not running. */\n index: number\n /** Indices of previously shown steps, oldest first. Drives `back`. */\n history: number[]\n /** Set when status is `aborted` or `paused`. */\n reason?: string\n}\n\nexport type EngineAction =\n | { type: 'start'; at?: number | string }\n | { type: 'next' }\n | { type: 'back' }\n | { type: 'go'; to: number | string }\n | { type: 'skip' }\n | { type: 'complete' }\n | { type: 'abort'; reason: string }\n | { type: 'pause'; reason: string }\n | { type: 'resume' }\n | { type: 'stepMissing' }\n | { type: 'stepSkipped' }\n\nexport interface EngineContext {\n tour: Tour\n /** Whether the step at `index` may be shown right now. */\n isEligible: (index: number) => boolean\n}\n\nexport const IDLE_STATE: EngineState = Object.freeze({ status: 'idle', index: -1, history: [] })\n\nexport function resolveStepIndex(tour: Tour, ref: number | string): number {\n if (typeof ref === 'number') return ref >= 0 && ref < tour.steps.length ? ref : -1\n return tour.steps.findIndex((s) => s.id === ref)\n}\n\n/** First eligible index at or after `from` (or at or before, when `dir` is -1). -1 if none. */\nexport function findEligible(ctx: EngineContext, from: number, dir: 1 | -1 = 1): number {\n const total = ctx.tour.steps.length\n for (let i = from; i >= 0 && i < total; i += dir) {\n if (ctx.isEligible(i)) return i\n }\n return -1\n}\n\nfunction advance(state: EngineState, ctx: EngineContext, record: boolean): EngineState {\n const next = findEligible(ctx, state.index + 1)\n if (next === -1) return { status: 'completed', index: state.index, history: state.history }\n return {\n status: 'running',\n index: next,\n history: record ? [...state.history, state.index] : state.history,\n }\n}\n\nexport function reduce(state: EngineState, action: EngineAction, ctx: EngineContext): EngineState {\n switch (action.type) {\n case 'start': {\n const at = action.at === undefined ? 0 : resolveStepIndex(ctx.tour, action.at)\n if (at === -1) return { status: 'aborted', index: -1, history: [], reason: 'unknown-step' }\n const first = findEligible(ctx, at)\n if (first === -1) {\n return { status: 'aborted', index: -1, history: [], reason: 'no-eligible-steps' }\n }\n return { status: 'running', index: first, history: [] }\n }\n\n case 'next':\n if (state.status !== 'running') return state\n return advance(state, ctx, true)\n\n case 'stepMissing': {\n if (state.status !== 'running') return state\n const step = ctx.tour.steps[state.index]\n if (step?.onMissing === 'abort') {\n return { ...state, status: 'aborted', reason: 'target-missing' }\n }\n // 'skip' and 'wait' (after the wait elapsed) both move on without recording history.\n return advance(state, ctx, false)\n }\n\n case 'stepSkipped':\n if (state.status !== 'running') return state\n return advance(state, ctx, false)\n\n case 'back': {\n if (state.status !== 'running') return state\n const history = [...state.history]\n let prev = history.pop()\n // Skip over history entries that are no longer eligible.\n while (prev !== undefined && !ctx.isEligible(prev)) prev = history.pop()\n if (prev === undefined) return state\n return { status: 'running', index: prev, history }\n }\n\n case 'go': {\n if (state.status !== 'running') return state\n const to = resolveStepIndex(ctx.tour, action.to)\n if (to === -1 || to === state.index || !ctx.isEligible(to)) return state\n return { status: 'running', index: to, history: [...state.history, state.index] }\n }\n\n case 'pause':\n if (state.status !== 'running') return state\n return { ...state, status: 'paused', reason: action.reason }\n\n case 'resume': {\n if (state.status !== 'paused') return state\n const { reason: _reason, ...rest } = state\n return { ...rest, status: 'running' }\n }\n\n case 'skip':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'skipped', index: state.index, history: state.history }\n\n case 'complete':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'completed', index: state.index, history: state.history }\n\n case 'abort':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { ...state, status: 'aborted', reason: action.reason }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Selectors\n// ---------------------------------------------------------------------------\n\nexport function isActive(state: EngineState): boolean {\n return state.status === 'running' || state.status === 'paused'\n}\n\nexport function isFinished(state: EngineState): boolean {\n return state.status === 'completed' || state.status === 'skipped' || state.status === 'aborted'\n}\n\nexport function canGoBack(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && state.history.some((i) => ctx.isEligible(i))\n}\n\n/** True when another eligible step follows. False on the last step. */\nexport function hasNext(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && findEligible(ctx, state.index + 1) !== -1\n}\n\nexport interface Progress {\n /** 1-based position of the current step. */\n current: number\n total: number\n}\n\n/** Position over all steps, ineligible ones included, so numbers stay stable. */\nexport function progress(state: EngineState, ctx: EngineContext): Progress {\n return { current: state.index + 1, total: ctx.tour.steps.length }\n}\n","/**\n * Orchestrates one tour: drives the pure reducer, talks to the renderer,\n * runs hooks, persists progress and emits events. This is the only stateful,\n * side-effectful piece of the core.\n */\n\nimport type { StepContext, TourHooks } from '../hooks'\nimport type { Step, Tour, TourProgressState } from '../schema/tour'\nimport { ANONYMOUS_IDENTITY, type EventSink, type Identity, type StorageAdapter } from '../seams'\nimport { type ConditionEnv, type CustomPredicate, evaluateCondition } from './conditions'\nimport { createEvent, NOOP_SINK } from './events'\nimport { createMemoryStorage, ProgressStore, type TourRecord, tourVersion } from './progress'\nimport {\n canGoBack,\n type EngineAction,\n type EngineContext,\n type EngineState,\n hasNext,\n IDLE_STATE,\n isFinished,\n progress,\n reduce,\n} from './reducer'\nimport type { RenderContext, Renderer } from './renderer'\nimport { matchRoute } from './route'\n\nexport interface ControllerOptions {\n tour: Tour\n renderer: Renderer\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n hooks?: TourHooks\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n /** Progress of other tours, for `tour` conditions. */\n tourState?: (tourId: string) => TourProgressState\n /** How long `onMissing: 'wait'` waits when the step sets no `waitFor`. */\n defaultWaitMs?: number\n now?: () => number\n}\n\nexport type StateListener = (state: EngineState) => void\n\nconst DEFAULT_WAIT_MS = 3000\n\nexport class TourController {\n readonly tour: Tour\n private state: EngineState = IDLE_STATE\n private readonly renderer: Renderer\n private readonly identity: Identity\n private readonly store: ProgressStore\n private readonly sink: EventSink\n private readonly hooks: TourHooks\n private readonly custom: Record<string, CustomPredicate>\n private readonly tourStateOf: ((tourId: string) => TourProgressState) | undefined\n private readonly defaultWaitMs: number\n private readonly now: () => number\n private readonly listeners = new Set<StateListener>()\n\n /** Bumped whenever an async flow must be abandoned. */\n private generation = 0\n private pendingAbort: AbortController | undefined\n private pendingTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(options: ControllerOptions) {\n this.tour = options.tour\n this.renderer = options.renderer\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.store = new ProgressStore(options.storage ?? createMemoryStorage())\n this.sink = options.sink ?? NOOP_SINK\n this.hooks = options.hooks ?? {}\n this.custom = options.custom ?? {}\n this.tourStateOf = options.tourState\n this.defaultWaitMs = options.defaultWaitMs ?? DEFAULT_WAIT_MS\n this.now = options.now ?? Date.now\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n getState(): EngineState {\n return this.state\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Start from the first eligible step, or from `at` (step id or index). */\n async start(at?: number | string): Promise<void> {\n if (this.state.status === 'running' || this.state.status === 'paused') return\n this.cancelPending()\n this.dispatch(at === undefined ? { type: 'start' } : { type: 'start', at })\n if (isFinished(this.state)) return this.finish()\n this.emit('tour:started')\n this.hooks.onStart?.(this.tour)\n await this.persist('in-progress')\n await this.showCurrent()\n }\n\n /** Start where the user left off, according to persisted progress. */\n async resume(): Promise<void> {\n const record = await this.store.get(this.tour.id)\n if (record?.state === 'in-progress' && record.version === tourVersion(this.tour)) {\n return this.start(record.stepId)\n }\n return this.start()\n }\n\n async next(): Promise<void> {\n if (this.state.status !== 'running') return\n await this.leaveCurrent()\n this.emit('step:completed', this.state.index)\n this.dispatch({ type: 'next' })\n await this.afterTransition()\n }\n\n async back(): Promise<void> {\n if (!canGoBack(this.state, this.context())) return\n await this.leaveCurrent()\n this.dispatch({ type: 'back' })\n await this.afterTransition()\n }\n\n async goTo(step: number | string): Promise<void> {\n if (this.state.status !== 'running') return\n const before = this.state\n const after = reduce(before, { type: 'go', to: step }, this.context())\n if (after === before) return\n await this.leaveCurrent()\n this.setState(after)\n await this.afterTransition()\n }\n\n /** The user gave up on the tour (Skip button, close, Escape). */\n async skip(): Promise<void> {\n if (!this.isActive()) return\n const ctx = this.stepContext()\n await this.leaveCurrent()\n this.dispatch({ type: 'skip' })\n await this.finish()\n if (ctx) this.hooks.onSkip?.(ctx)\n }\n\n async abort(reason: string): Promise<void> {\n if (!this.isActive()) return\n await this.leaveCurrent()\n this.dispatch({ type: 'abort', reason })\n await this.finish()\n }\n\n /** Report a named application event. Advances a step waiting on it. */\n notify(eventName: string): void {\n const step = this.currentStep()\n const advance = step?.advance\n if (typeof advance === 'object' && advance.on === 'event' && advance.name === eventName) {\n void this.next()\n }\n }\n\n /** Tell the controller the route changed. Pauses or resumes route-bound steps. */\n async routeChanged(): Promise<void> {\n const step = this.currentStep()\n if (!step) return\n const onRoute = this.stepOnRoute(step)\n if (this.state.status === 'paused' && this.state.reason === 'route' && onRoute) {\n this.dispatch({ type: 'resume' })\n await this.showCurrent()\n } else if (this.state.status === 'running' && !onRoute) {\n this.cancelPending()\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n }\n }\n\n /** Stop everything and clear the screen without recording an outcome. */\n async destroy(): Promise<void> {\n this.cancelPending()\n this.listeners.clear()\n // Reset before awaiting: a start() that runs while hide() settles must not\n // be overwritten afterwards (React StrictMode destroys, then reuses).\n const hidden = this.renderer.hide()\n this.state = IDLE_STATE\n await hidden\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isActive(): boolean {\n return this.state.status === 'running' || this.state.status === 'paused'\n }\n\n private currentStep(): Step | undefined {\n return this.isActive() ? this.tour.steps[this.state.index] : undefined\n }\n\n private conditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.renderer.hasTarget(t),\n custom: this.custom,\n }\n const route = this.renderer.currentRoute?.()\n if (route !== undefined) env.route = route\n if (this.tourStateOf) env.tourState = this.tourStateOf\n return env\n }\n\n private context(): EngineContext {\n const env = this.conditionEnv()\n return {\n tour: this.tour,\n isEligible: (i) => {\n const step = this.tour.steps[i]\n if (!step) return false\n return step.condition ? evaluateCondition(step.condition, env) : true\n },\n }\n }\n\n private stepOnRoute(step: Step): boolean {\n const route = this.renderer.currentRoute?.()\n if (!step.route || route === undefined) return true\n return matchRoute(step.route, route)\n }\n\n private dispatch(action: EngineAction): void {\n this.setState(reduce(this.state, action, this.context()))\n }\n\n private setState(next: EngineState): void {\n if (next === this.state) return\n this.state = next\n for (const l of this.listeners) l(next)\n }\n\n private stepContext(): StepContext | undefined {\n const step = this.currentStep()\n if (!step) return undefined\n return { tour: this.tour, step, index: this.state.index, total: this.tour.steps.length }\n }\n\n private emit(type: Parameters<typeof createEvent>[0], stepIndex?: number): void {\n const input: Parameters<typeof createEvent>[1] = {\n tour: this.tour,\n identity: this.identity,\n now: this.now,\n }\n if (stepIndex !== undefined) input.stepIndex = stepIndex\n this.sink.emit(createEvent(type, input))\n }\n\n private async persist(state: TourProgressState): Promise<void> {\n const record: TourRecord = {\n tourId: this.tour.id,\n version: tourVersion(this.tour),\n state,\n updatedAt: this.now(),\n }\n const step = this.currentStep()\n if (state === 'in-progress' && step) record.stepId = step.id\n await this.store.set(record)\n }\n\n private cancelPending(): void {\n this.generation++\n this.pendingAbort?.abort()\n this.pendingAbort = undefined\n if (this.pendingTimer !== undefined) clearTimeout(this.pendingTimer)\n this.pendingTimer = undefined\n }\n\n /** Run `beforeHide` for the step being left, if any. */\n private async leaveCurrent(): Promise<void> {\n const ctx = this.stepContext()\n this.cancelPending()\n if (ctx && this.state.status === 'running') {\n await this.hooks.steps?.[ctx.step.id]?.beforeHide?.(ctx)\n }\n }\n\n private async afterTransition(): Promise<void> {\n if (this.state.status === 'running') return this.showCurrent()\n if (isFinished(this.state)) return this.finish()\n }\n\n private async finish(): Promise<void> {\n this.cancelPending()\n await this.renderer.hide()\n switch (this.state.status) {\n case 'completed':\n this.emit('tour:completed')\n await this.persist('completed')\n this.hooks.onComplete?.(this.tour)\n break\n case 'skipped':\n this.emit('tour:skipped')\n await this.persist('skipped')\n break\n case 'aborted':\n this.emit('tour:aborted')\n await this.persist('skipped')\n this.hooks.onAbort?.(this.tour, this.state.reason ?? 'unknown')\n break\n }\n }\n\n private async showCurrent(): Promise<void> {\n this.cancelPending()\n const generation = this.generation\n const stale = () => generation !== this.generation\n const ctx = this.stepContext()\n if (!ctx) return\n const { step } = ctx\n\n if (!this.stepOnRoute(step)) {\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n return\n }\n\n const proceed = await this.hooks.steps?.[step.id]?.beforeShow?.(ctx)\n if (stale()) return\n if (proceed === false) {\n this.emit('step:skipped', this.state.index)\n this.dispatch({ type: 'stepSkipped' })\n return this.afterTransition()\n }\n\n if (step.target !== undefined && !(await this.ensureTarget(step, generation))) {\n if (stale()) return\n this.emit('step:missing', this.state.index)\n this.dispatch({ type: 'stepMissing' })\n return this.afterTransition()\n }\n if (stale()) return\n\n await this.renderer.show(this.renderContext(ctx))\n if (stale()) return\n this.emit('step:shown', this.state.index)\n this.hooks.onStepChange?.(ctx)\n await this.persist('in-progress')\n await this.hooks.steps?.[step.id]?.afterShow?.(ctx)\n if (stale()) return\n this.armAdvance(step, generation)\n }\n\n /** Resolve `true` when the target is present, waiting if the step allows it. */\n private async ensureTarget(step: Step, generation: number): Promise<boolean> {\n const target = step.target\n if (target === undefined) return true\n if (this.renderer.hasTarget(target)) return true\n const waitMs = step.waitFor ?? (step.onMissing === 'wait' ? this.defaultWaitMs : 0)\n if (waitMs <= 0) return false\n const abort = new AbortController()\n this.pendingAbort = abort\n const found = await this.renderer.waitForTarget(target, waitMs, abort.signal)\n if (generation !== this.generation) return false\n this.pendingAbort = undefined\n return found\n }\n\n /** Set up automatic advancement for `delay` and `element` steps. */\n private armAdvance(step: Step, generation: number): void {\n const advance = step.advance\n if (typeof advance !== 'object') return\n if (advance.on === 'delay') {\n this.pendingTimer = setTimeout(() => {\n this.pendingTimer = undefined\n if (generation === this.generation) void this.next()\n }, advance.ms)\n } else if (advance.on === 'element') {\n const abort = new AbortController()\n this.pendingAbort = abort\n void this.renderer\n .waitForTarget(advance.target, Number.POSITIVE_INFINITY, abort.signal)\n .then((found) => {\n if (found && generation === this.generation) void this.next()\n })\n }\n }\n\n private renderContext(ctx: StepContext): RenderContext {\n const engineCtx = this.context()\n return {\n tour: this.tour,\n step: ctx.step,\n index: ctx.index,\n progress: progress(this.state, engineCtx),\n isFirst: this.state.history.length === 0,\n isLast: !hasNext(this.state, engineCtx),\n canGoBack: canGoBack(this.state, engineCtx),\n actions: {\n next: () => void this.next(),\n back: () => void this.back(),\n skip: () => void this.skip(),\n goTo: (s) => void this.goTo(s),\n },\n }\n }\n}\n","import type { StorageAdapter } from '../seams'\n\n/**\n * Prefix every key with the user id so progress on a shared browser (or a\n * developer switching test accounts) never leaks between users.\n * Anonymous visitors use the unprefixed keys.\n */\nexport function scopeStorage(base: StorageAdapter, userId: string | undefined): StorageAdapter {\n if (!userId) return base\n const prefix = `u:${userId}:`\n return {\n get: (key) => base.get(prefix + key),\n set: (key, value) => base.set(prefix + key, value),\n remove: (key) => base.remove(prefix + key),\n }\n}\n","/**\n * The tour manager. Holds many tours, watches their triggers, checks\n * conditions and frequency, and starts at most one at a time. This is what\n * turns the rules in the tour JSON into behaviour.\n */\n\nimport { type ConditionEnv, type CustomPredicate, evaluateAll } from '../engine/conditions'\nimport type { ControllerOptions, TourController } from '../engine/controller'\nimport {\n createMemoryStorage,\n ProgressStore,\n shouldShow,\n type TourRecord,\n tourVersion,\n} from '../engine/progress'\nimport { matchRoute } from '../engine/route'\nimport type { TourHooks } from '../hooks'\nimport type { Tour, TourProgressState, TraitValue, Trigger } from '../schema/tour'\nimport {\n ANONYMOUS_IDENTITY,\n type DocentEvent,\n type EventSink,\n type Identity,\n type StorageAdapter,\n type TourSource,\n} from '../seams'\nimport type { DocentEnvironment } from './environment'\nimport { scopeStorage } from './scoped-storage'\n\n/** Options every controller the manager creates receives. */\nexport type SharedControllerOptions = Omit<ControllerOptions, 'tour' | 'renderer'>\n\nexport interface DocentOptions {\n /** Tours to manage: an array, or a source that loads (and may live-update) them. */\n tours?: Tour[] | TourSource\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n /** Hooks per tour, keyed by tour id. */\n hooks?: Record<string, TourHooks>\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n environment: DocentEnvironment\n /** Builds a platform controller for a tour. The DOM package supplies this. */\n createController: (tour: Tour, options: SharedControllerOptions) => TourController\n now?: () => number\n /**\n * Start watching triggers right away. Default true. Framework bindings pass\n * `false` and call `connect()` / `disconnect()` from their mount lifecycle,\n * which keeps construction free of side effects (React StrictMode).\n */\n connect?: boolean\n}\n\nexport interface DocentState {\n /** Id of the tour currently running or paused, if any. */\n active: string | null\n /** Tours known to the manager. */\n tours: string[]\n}\n\nexport type DocentListener = (state: DocentState) => void\n\nexport interface StartOptions {\n /** Step id or index to start from. */\n at?: number | string\n}\n\ntype Cleanup = () => void\n\nfunction isTourSource(value: Tour[] | TourSource | undefined): value is TourSource {\n return !!value && !Array.isArray(value) && typeof (value as TourSource).load === 'function'\n}\n\nexport class Docent {\n private readonly options: DocentOptions\n private readonly env: DocentEnvironment\n private readonly baseStorage: StorageAdapter\n private identity: Identity\n private store: ProgressStore\n private tours = new Map<string, Tour>()\n private records = new Map<string, TourRecord | null>()\n private controller: TourController | undefined\n private activeId: string | null = null\n /** Tours whose trigger fired while another tour was running. */\n private queue: string[] = []\n private triggerCleanups: Cleanup[] = []\n /** `auto` triggers fire once per manager instance (per page load), not on every re-arm. */\n private readonly autoFired = new Set<string>()\n private readonly cleanups: Cleanup[] = []\n private readonly timers = new Set<ReturnType<typeof setTimeout>>()\n private readonly listeners = new Set<DocentListener>()\n private readonly eventListeners = new Set<(event: DocentEvent) => void>()\n private destroyed = false\n private loading: Promise<void> | undefined\n /** Wanted by the owner (between connect and disconnect). */\n private connected = false\n /** Listening to routes, the source and triggers. */\n private attached = false\n\n constructor(options: DocentOptions) {\n this.options = options\n this.env = options.environment\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.baseStorage = options.storage ?? createMemoryStorage()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, this.identity.id))\n if (options.connect !== false) this.connect()\n }\n\n /** Resolves once tours and progress are loaded. Loading starts on first use. */\n get ready(): Promise<void> {\n this.loading ??= this.load()\n return this.loading\n }\n\n /** Start watching routes, the tour source and triggers. Idempotent. */\n connect(): void {\n if (this.destroyed || this.connected) return\n this.connected = true\n void this.ready.then(() => {\n if (this.connected && !this.attached && !this.destroyed) this.attach()\n })\n }\n\n /**\n * Stop watching and remove any running tour without recording an outcome.\n * `connect()` resumes. Unlike `destroy()`, the manager stays usable.\n */\n async disconnect(): Promise<void> {\n this.connected = false\n if (this.attached) {\n this.attached = false\n this.disarmTriggers()\n for (const c of this.cleanups) c()\n this.cleanups.length = 0\n }\n this.queue = []\n await this.stopActive()\n this.emitState()\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** The tours currently managed. */\n getTours(): Tour[] {\n return [...this.tours.values()]\n }\n\n /**\n * Observe every lifecycle event from every tour, in addition to the `sink`\n * option. Returns an unsubscribe function. Used by devtools.\n */\n onEvent(listener: (event: DocentEvent) => void): () => void {\n this.eventListeners.add(listener)\n return () => this.eventListeners.delete(listener)\n }\n\n getState(): DocentState {\n return { active: this.activeId, tours: [...this.tours.keys()] }\n }\n\n subscribe(listener: DocentListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** The controller of the running tour, for fine-grained control. */\n get activeController(): TourController | undefined {\n return this.controller\n }\n\n /**\n * Set who the user is. Progress is stored per user id, and traits feed\n * `trait` conditions. Re-evaluates triggers, since the user may now qualify.\n */\n async identify(id: string | undefined, traits: Record<string, TraitValue> = {}): Promise<void> {\n await this.ready\n const userChanged = id !== this.identity.id\n this.identity = id === undefined ? { traits } : { id, traits }\n if (userChanged) {\n this.autoFired.clear()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, id))\n await this.loadRecords()\n }\n this.armTriggers()\n }\n\n /**\n * Report something that happened in your app. Starts tours with a matching\n * `event` trigger and advances a running step waiting on that event.\n */\n track(eventName: string): void {\n this.controller?.notify(eventName)\n for (const tour of this.tours.values()) {\n const t = tour.trigger\n if (t?.type === 'event' && t.name === eventName) this.fire(tour.id)\n }\n }\n\n /**\n * Start a tour now, ignoring its trigger, conditions and frequency. Use for\n * \"take the tour\" buttons. Stops any tour already running.\n */\n async start(tourId: string, options: StartOptions = {}): Promise<boolean> {\n await this.ready\n const tour = this.tours.get(tourId)\n if (!tour) return false\n await this.stopActive()\n await this.run(tour, options.at, true)\n return true\n }\n\n /** Whether a tour's conditions hold and its frequency allows showing it now. */\n isEligible(tourId: string): boolean {\n const tour = this.tours.get(tourId)\n if (!tour) return false\n return (\n shouldShow(tour, this.records.get(tourId) ?? null) &&\n evaluateAll(tour.conditions, this.getConditionEnv())\n )\n }\n\n /** Progress state of a tour for the current user. */\n tourState(tourId: string): TourProgressState {\n const tour = this.tours.get(tourId)\n const record = this.records.get(tourId)\n if (!record) return 'not-started'\n if (tour && record.version < tourVersion(tour)) return 'not-started'\n return record.state\n }\n\n /** Forget progress for one tour, or all of them, so they show again. */\n async reset(tourId?: string): Promise<void> {\n await this.ready\n const ids = tourId ? [tourId] : [...this.tours.keys()]\n for (const id of ids) {\n await this.store.clear(id)\n this.records.set(id, null)\n this.autoFired.delete(id)\n }\n this.armTriggers()\n }\n\n /**\n * Re-check triggers and tell the running tour the route may have changed.\n * Call after navigation if your router does not emit browser navigation events.\n */\n async refresh(): Promise<void> {\n await this.ready\n this.armTriggers()\n await this.controller?.routeChanged()\n }\n\n /** Stop the running tour (recorded as skipped). */\n async stop(): Promise<void> {\n await this.controller?.skip()\n }\n\n async destroy(): Promise<void> {\n await this.disconnect()\n this.destroyed = true\n this.listeners.clear()\n this.eventListeners.clear()\n }\n\n // -------------------------------------------------------------------------\n // Loading\n // -------------------------------------------------------------------------\n\n private async load(): Promise<void> {\n const source = this.options.tours\n const initial = isTourSource(source) ? await source.load() : (source ?? [])\n this.setTours(initial)\n await this.loadRecords()\n }\n\n private attach(): void {\n this.attached = true\n const source = this.options.tours\n if (isTourSource(source) && source.subscribe) {\n this.cleanups.push(\n source.subscribe((tours) => {\n this.setTours(tours)\n void this.loadRecords().then(() => this.armTriggers())\n }),\n )\n }\n if (this.env.onRouteChange) {\n this.cleanups.push(this.env.onRouteChange(() => this.armTriggers()))\n }\n this.armTriggers()\n }\n\n private setTours(tours: Tour[]): void {\n this.tours = new Map(tours.map((t) => [t.id, t]))\n this.queue = this.queue.filter((id) => this.tours.has(id))\n this.emitState()\n }\n\n private async loadRecords(): Promise<void> {\n const entries = await Promise.all(\n [...this.tours.keys()].map(async (id) => [id, await this.store.get(id)] as const),\n )\n this.records = new Map(entries)\n }\n\n // -------------------------------------------------------------------------\n // Triggers\n // -------------------------------------------------------------------------\n\n private disarmTriggers(): void {\n for (const c of this.triggerCleanups) c()\n this.triggerCleanups = []\n for (const t of this.timers) clearTimeout(t)\n this.timers.clear()\n }\n\n /**\n * (Re)arm every trigger. Cheap; called on load, identify, route change,\n * source updates and after a tour finishes (so chained tours can start).\n * `except` skips one tour, used for the tour that just finished.\n */\n private armTriggers(except?: string): void {\n if (this.destroyed || !this.attached) return\n this.disarmTriggers()\n for (const tour of this.tours.values()) {\n const trigger = tour.trigger\n if (!trigger || tour.id === except) continue\n this.arm(tour, trigger)\n }\n }\n\n private arm(tour: Tour, trigger: Trigger): void {\n switch (trigger.type) {\n case 'manual':\n case 'event':\n return\n case 'auto':\n // Marked as used in fire() only once it actually starts or queues, so a tour\n // that was not eligible at load can still start when the user qualifies.\n if (this.autoFired.has(tour.id)) return\n this.fireAfter(tour.id, trigger.delay)\n return\n case 'route': {\n const route = this.env.currentRoute?.()\n if (route !== undefined && matchRoute(trigger.pattern, route))\n this.fireAfter(tour.id, trigger.delay)\n return\n }\n case 'element': {\n if (!this.env.watchTarget) {\n if (this.env.hasTarget(trigger.target)) this.fireAfter(tour.id, trigger.delay)\n return\n }\n this.triggerCleanups.push(\n this.env.watchTarget(trigger.target, () => this.fireAfter(tour.id, trigger.delay)),\n )\n }\n }\n }\n\n private fireAfter(tourId: string, delay: number | undefined): void {\n if (!delay) {\n this.fire(tourId)\n return\n }\n const timer = setTimeout(() => {\n this.timers.delete(timer)\n this.fire(tourId)\n }, delay)\n this.timers.add(timer)\n }\n\n /** A trigger fired: start the tour if eligible, or queue it behind the running one. */\n private fire(tourId: string): void {\n if (this.destroyed || !this.attached || tourId === this.activeId) return\n if (!this.isEligible(tourId) || !this.triggerStillHolds(tourId)) return\n if (this.tours.get(tourId)?.trigger?.type === 'auto') this.autoFired.add(tourId)\n if (this.activeId) {\n if (!this.queue.includes(tourId)) this.queue.push(tourId)\n return\n }\n const tour = this.tours.get(tourId)\n if (tour) void this.run(tour, undefined, false)\n }\n\n /** Route triggers are only valid while the user is still on a matching route. */\n private triggerStillHolds(tourId: string): boolean {\n const trigger = this.tours.get(tourId)?.trigger\n if (trigger?.type !== 'route') return true\n const route = this.env.currentRoute?.()\n return route !== undefined && matchRoute(trigger.pattern, route)\n }\n\n // -------------------------------------------------------------------------\n // Running\n // -------------------------------------------------------------------------\n\n /** What conditions are evaluated against right now. Used by `isEligible` and devtools. */\n getConditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.env.hasTarget(t),\n tourState: (id) => this.tourState(id),\n custom: this.options.custom ?? {},\n }\n const route = this.env.currentRoute?.()\n if (route !== undefined) env.route = route\n return env\n }\n\n private sharedOptions(tour: Tour): SharedControllerOptions {\n const shared: SharedControllerOptions = {\n identity: this.identity,\n storage: scopeStorage(this.baseStorage, this.identity.id),\n tourState: (id) => this.tourState(id),\n }\n shared.sink = {\n emit: (event) => {\n this.options.sink?.emit(event)\n for (const l of this.eventListeners) l(event)\n },\n }\n const hooks = this.options.hooks?.[tour.id]\n if (hooks) shared.hooks = hooks\n if (this.options.custom) shared.custom = this.options.custom\n if (this.options.now) shared.now = this.options.now\n return shared\n }\n\n private async run(tour: Tour, at: number | string | undefined, manual: boolean): Promise<void> {\n this.queue = this.queue.filter((id) => id !== tour.id)\n const controller = this.options.createController(tour, this.sharedOptions(tour))\n this.controller = controller\n this.activeId = tour.id\n // Mirror what the controller persists, so tourState() is right while it runs.\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state: 'in-progress',\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.emitState()\n\n const off = controller.subscribe((state) => {\n if (\n state.status === 'completed' ||\n state.status === 'skipped' ||\n state.status === 'aborted'\n ) {\n off()\n this.finished(controller, tour, state.status === 'completed' ? 'completed' : 'skipped')\n }\n })\n\n const resume = !manual && at === undefined && tour.options?.persist\n if (resume) await controller.resume()\n else await controller.start(at)\n\n // start() returned without running (nothing happened): release the slot.\n if (controller.getState().status === 'idle' && this.controller === controller) {\n off()\n this.release(controller)\n }\n }\n\n /**\n * The controller emits the final status before it finishes writing storage,\n * so record the outcome here directly instead of reading it back.\n */\n private finished(controller: TourController, tour: Tour, state: 'completed' | 'skipped'): void {\n if (this.controller !== controller) return\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state,\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.release(controller, tour.id)\n }\n\n private release(controller: TourController, finishedId?: string): void {\n this.controller = undefined\n this.activeId = null\n this.emitState()\n // Let the finished controller complete its own cleanup (hide, persist, events) first.\n setTimeout(() => {\n void controller.destroy()\n this.drainQueue()\n // Tours whose conditions depend on the one that just ended may now qualify.\n if (!this.activeId) this.armTriggers(finishedId)\n }, 0)\n }\n\n /** Stop the running tour without recording an outcome (used before a manual start). */\n private async stopActive(): Promise<void> {\n const controller = this.controller\n if (!controller) return\n this.controller = undefined\n this.activeId = null\n await controller.destroy()\n }\n\n private drainQueue(): void {\n while (this.queue.length > 0 && !this.activeId) {\n const next = this.queue.shift()\n if (next) this.fire(next)\n }\n }\n\n private emitState(): void {\n const state = this.getState()\n for (const l of this.listeners) l(state)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/schema/tour.ts","../src/define.ts","../src/engine/route.ts","../src/engine/conditions.ts","../src/seams.ts","../src/engine/progress.ts","../src/engine/events.ts","../src/engine/reducer.ts","../src/engine/controller.ts","../src/manager/scoped-storage.ts","../src/manager/docent.ts"],"sourcesContent":["/**\n * Tour schema — the JSON contract shared by the core engine, every renderer,\n * the visual builder and the hosted service.\n *\n * Everything in this file must stay JSON-serialisable. Functions (hooks) live\n * in `hooks.ts` and are attached at runtime, keyed by step id.\n */\n\n/** Current schema version. Bump only on breaking changes to this file. */\nexport const SCHEMA_VERSION = 1 as const\n\nexport type SchemaVersion = typeof SCHEMA_VERSION\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\n/**\n * Where a step points.\n *\n * - A bare string is a CSS selector (web only). Convenient for hand-written tours.\n * - A {@link TargetSpec} is the portable, self-healing form the builder produces.\n *\n * Prefer `{ name }` over raw selectors: on the web it resolves to\n * `[data-docent=\"<name>\"]`, on native to a `testID`, so one tour works everywhere.\n */\nexport type Target = string | TargetSpec\n\nexport interface TargetSpec {\n /**\n * Logical name. Web: `[data-docent=\"<name>\"]`. Native: `testID` / `nativeID`.\n * The most robust anchor; survives refactors and works cross-platform.\n */\n name?: string\n /**\n * Ordered CSS selector fallbacks (web only). Tried in order until one matches.\n * The builder fills several so a tour keeps working after markup changes.\n */\n selectors?: string[]\n /** Explicit native identifier when it differs from `name`. */\n native?: string\n /** Restrict the search to a container matched by this selector. */\n within?: string\n /** When a selector matches several elements, pick this index (default 0). */\n nth?: number\n}\n\n// ---------------------------------------------------------------------------\n// Placement & visuals\n// ---------------------------------------------------------------------------\n\nexport type Side = 'top' | 'right' | 'bottom' | 'left'\nexport type Alignment = 'start' | 'center' | 'end'\n\n/**\n * Preferred popover position relative to the target. `auto` lets the renderer\n * pick the side with the most room. Any placement flips or shifts when it\n * would overflow the viewport.\n */\nexport type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`\n\n/**\n * What connects the popover to its target.\n * - `caret` (default): a small notch on the popover's edge.\n * - `none`: nothing.\n * - Connectors, drawn from the popover to the target: `line`, `dashed`,\n * `dotted`, `curve`, `curve-dashed`, `squiggle`, `loop`, `elbow`, `sketch`\n * (hand-drawn double stroke) and `pin` (dotted line ending in a dot).\n */\nexport type ArrowStyle =\n | 'caret'\n | 'none'\n | 'line'\n | 'dashed'\n | 'dotted'\n | 'curve'\n | 'curve-dashed'\n | 'squiggle'\n | 'loop'\n | 'elbow'\n | 'sketch'\n | 'pin'\n\n/** Shape of the cutout around the target. `circle` circumscribes the target. */\nexport type SpotlightShape = 'rounded' | 'rect' | 'pill' | 'circle'\n\n/** Outline drawn around the cutout. `pulse` gently repeats to draw the eye. */\nexport type SpotlightRing = 'hairline' | 'none' | 'glow' | 'pulse' | 'dashed' | 'solid'\n\n/**\n * How the rest of the page is treated.\n * - `dim` (default): a tinted scrim.\n * - `blur`: scrim plus a soft blur of the page.\n * - `vignette`: clear near the target, darker toward the edges.\n * - `none`: no scrim and the page stays usable (hint-style tours).\n */\nexport type OverlayStyle = 'dim' | 'blur' | 'vignette' | 'none'\n\nexport interface SpotlightOptions {\n /** Space between the target's edge and the cutout, in px. */\n padding?: number\n /** Corner radius of the cutout, in px (for `rounded`). */\n radius?: number\n /** Animate the cutout moving between targets. */\n animate?: boolean\n shape?: SpotlightShape\n ring?: SpotlightRing\n}\n\nexport interface OverlayOptions {\n style?: OverlayStyle\n /** Backdrop colour, any CSS colour. */\n color?: string\n /** Backdrop opacity, 0–1. */\n opacity?: number\n /** Blur radius for the `blur` style, in px. */\n blur?: number\n}\n\nexport interface ScrollOptions {\n /** Scroll the target into view before showing the step. */\n enabled?: boolean\n behavior?: 'auto' | 'smooth'\n block?: 'start' | 'center' | 'end' | 'nearest'\n}\n\nexport interface Media {\n type: 'image' | 'video'\n src: string\n alt?: string\n}\n\n// ---------------------------------------------------------------------------\n// Behaviour\n// ---------------------------------------------------------------------------\n\n/**\n * How a step completes.\n *\n * - `'button'` (default): the user presses Next.\n * - `click` / `input`: the user interacts with the target (or another element).\n * - `event`: a named event is emitted through the runtime.\n * - `element`: an element appears (e.g. a menu the user was asked to open).\n * - `delay`: automatically after `ms`.\n */\nexport type Advance =\n | 'button'\n | { on: 'click'; target?: Target }\n | { on: 'input'; target?: Target; match?: string }\n | { on: 'event'; name: string }\n | { on: 'element'; target: Target }\n | { on: 'delay'; ms: number }\n\n/** Whether the user can interact with the spotlighted target. */\nexport type Interaction = 'block' | 'allow'\n\n/** What to do when a step's target cannot be found. */\nexport type OnMissing = 'skip' | 'wait' | 'abort'\n\nexport interface StepButtons {\n back?: boolean\n next?: boolean\n skip?: boolean\n close?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Conditions & triggers\n// ---------------------------------------------------------------------------\n\nexport type TraitValue = string | number | boolean | null | string[]\n\nexport type TraitOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'in'\n | 'nin'\n | 'contains'\n | 'exists'\n | 'missing'\n\n/**\n * Serialisable predicate evaluated by the core at runtime.\n * `custom` predicates are registered by name on the runtime.\n */\nexport type Condition =\n | { type: 'trait'; key: string; op: TraitOperator; value?: TraitValue }\n | { type: 'route'; pattern: string }\n | { type: 'element'; target: Target; exists?: boolean }\n | { type: 'tour'; id: string; state: TourProgressState }\n | { type: 'all'; conditions: Condition[] }\n | { type: 'any'; conditions: Condition[] }\n | { type: 'not'; condition: Condition }\n | { type: 'custom'; name: string; args?: Record<string, TraitValue> }\n\nexport type TourProgressState = 'not-started' | 'in-progress' | 'completed' | 'skipped'\n\n/** What starts a tour. `manual` means only through the runtime API. */\nexport type Trigger =\n | { type: 'manual' }\n | { type: 'auto'; delay?: number }\n | { type: 'route'; pattern: string; delay?: number }\n | { type: 'element'; target: Target; delay?: number }\n | { type: 'event'; name: string }\n\n/**\n * How often an eligible user sees the tour.\n * - `once`: show once per {@link Tour.version}, however it ended.\n * - `until-completed`: keep offering it until the user finishes it.\n * - `always`: every time the trigger fires.\n */\nexport type Frequency = 'once' | 'until-completed' | 'always'\n\n// ---------------------------------------------------------------------------\n// Theme\n// ---------------------------------------------------------------------------\n\n/**\n * Visual tokens, serialisable so a builder or customizer can produce them.\n * Each maps to a CSS custom property in the renderer (`--docent-*`).\n * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.\n */\nexport interface Theme {\n /** Color of drawn connectors (arrow styles other than caret). */\n connector?: string\n /** Color of the spotlight ring. */\n ring?: string\n background?: string\n foreground?: string\n muted?: string\n accent?: string\n accentForeground?: string\n radius?: string\n shadow?: string\n font?: string\n width?: string\n overlay?: string\n overlayOpacity?: string\n duration?: string\n zIndex?: string\n}\n\n// ---------------------------------------------------------------------------\n// Labels\n// ---------------------------------------------------------------------------\n\nexport interface Labels {\n next?: string\n back?: string\n skip?: string\n done?: string\n close?: string\n /** Supports `{current}` and `{total}` placeholders. */\n progress?: string\n}\n\n// ---------------------------------------------------------------------------\n// Step\n// ---------------------------------------------------------------------------\n\nexport interface Step {\n /** Unique within the tour. Used for persistence, hooks and analytics. */\n id: string\n /** Omit for a centred modal step (welcome / finish screens). */\n target?: Target\n title?: string\n body?: string\n /** How `body` is interpreted. Renderers never inject raw HTML. */\n format?: 'text' | 'markdown'\n media?: Media\n placement?: Placement\n /** Per-step override of the tour's arrow style. */\n arrow?: ArrowStyle\n /** Per-step override of the tour's spotlight options. */\n spotlight?: SpotlightOptions\n /** Per-step override of the tour's overlay options. */\n overlay?: OverlayOptions\n advance?: Advance\n interaction?: Interaction\n /** Skip this step when the condition is false. */\n condition?: Condition\n onMissing?: OnMissing\n /** How long to wait for the target when `onMissing` is `wait`, in ms. */\n waitFor?: number\n /** URL pattern this step belongs to. Enables multi-page tours. */\n route?: string\n buttons?: StepButtons\n scroll?: ScrollOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Tour\n// ---------------------------------------------------------------------------\n\nexport interface TourOptions {\n /** Persist progress so the tour survives navigation and reloads. */\n persist?: boolean\n frequency?: Frequency\n showProgress?: boolean\n /** Allow closing with Escape or the close button. */\n allowClose?: boolean\n closeOnOverlayClick?: boolean\n keyboard?: boolean\n arrow?: ArrowStyle\n spotlight?: SpotlightOptions\n overlay?: OverlayOptions\n scroll?: ScrollOptions\n labels?: Labels\n /** Visual tokens applied on top of the renderer's theme. */\n theme?: Theme\n /** Name of a template registered on the renderer (slots, css, theme). */\n template?: string\n}\n\nexport interface Tour {\n schemaVersion: SchemaVersion\n /** Stable identifier. Used for persistence, targeting and analytics. */\n id: string\n /** Bump to re-show the tour to users who already saw an older version. */\n version?: number\n /** Human-readable name, mainly for the builder and dashboards. */\n name?: string\n description?: string\n steps: Step[]\n trigger?: Trigger\n /** All must hold for the tour to be eligible. */\n conditions?: Condition[]\n options?: TourOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n","import { SCHEMA_VERSION, type SchemaVersion, type Tour } from './schema/tour'\n\n/**\n * Identity helper that gives hand-written tours full type inference and\n * fills in the schema version. Returns the same object.\n */\nexport function defineTour(\n tour: Omit<Tour, 'schemaVersion'> & { schemaVersion?: SchemaVersion },\n): Tour {\n return { ...tour, schemaVersion: SCHEMA_VERSION }\n}\n","/**\n * Route pattern matching for `route` triggers, conditions and step routes.\n *\n * Patterns are path globs:\n * - `/settings` exact\n * - `/users/:id` one segment (named for readability, value ignored)\n * - `/users/*` one segment\n * - `/docs/**` zero or more segments\n *\n * Query strings and hashes are ignored. Trailing slashes are tolerated.\n */\n\nfunction segments(path: string): string[] {\n const clean = path.split(/[?#]/, 1)[0] ?? ''\n return clean.split('/').filter(Boolean)\n}\n\nfunction matchSegments(pattern: string[], path: string[], pi = 0, si = 0): boolean {\n if (pi === pattern.length) return si === path.length\n const p = pattern[pi]\n if (p === '**') {\n for (let k = si; k <= path.length; k++) {\n if (matchSegments(pattern, path, pi + 1, k)) return true\n }\n return false\n }\n if (si === path.length) return false\n if (p === '*' || p?.startsWith(':') || p === path[si]) {\n return matchSegments(pattern, path, pi + 1, si + 1)\n }\n return false\n}\n\nexport function matchRoute(pattern: string, path: string): boolean {\n return matchSegments(segments(pattern), segments(path))\n}\n","/**\n * Evaluates serialisable {@link Condition}s against a runtime environment.\n * Pure: everything it needs is passed in.\n */\n\nimport type { Condition, Target, TourProgressState, TraitValue } from '../schema/tour'\nimport type { Identity } from '../seams'\nimport { matchRoute } from './route'\n\nexport type CustomPredicate = (args?: Record<string, TraitValue>) => boolean\n\nexport interface ConditionEnv {\n identity: Identity\n /** Current path, e.g. `/invoices/new`. */\n route?: string\n elementExists?: (target: Target) => boolean\n tourState?: (tourId: string) => TourProgressState\n custom?: Record<string, CustomPredicate>\n}\n\nfunction sameValue(a: TraitValue | undefined, b: TraitValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => v === b[i])\n }\n return a === b\n}\n\nfunction compare(a: TraitValue | undefined, b: TraitValue | undefined): number | null {\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n return null\n}\n\nfunction contains(haystack: TraitValue | undefined, needle: TraitValue | undefined): boolean {\n if (Array.isArray(haystack)) return typeof needle === 'string' && haystack.includes(needle)\n if (typeof haystack === 'string') {\n return (\n (typeof needle === 'string' || typeof needle === 'number') &&\n haystack.includes(String(needle))\n )\n }\n return false\n}\n\nfunction inList(value: TraitValue | undefined, list: TraitValue | undefined): boolean {\n if (!Array.isArray(list)) return false\n if (Array.isArray(value)) return value.some((v) => list.includes(v))\n return typeof value === 'string' && list.includes(value)\n}\n\nexport function evaluateTrait(\n actual: TraitValue | undefined,\n op: Extract<Condition, { type: 'trait' }>['op'],\n expected: TraitValue | undefined,\n): boolean {\n switch (op) {\n case 'exists':\n return actual !== undefined && actual !== null\n case 'missing':\n return actual === undefined || actual === null\n case 'eq':\n return sameValue(actual, expected)\n case 'neq':\n return !sameValue(actual, expected)\n case 'in':\n return inList(actual, expected)\n case 'nin':\n return !inList(actual, expected)\n case 'contains':\n return contains(actual, expected)\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const c = compare(actual, expected)\n if (c === null) return false\n if (op === 'gt') return c > 0\n if (op === 'gte') return c >= 0\n if (op === 'lt') return c < 0\n return c <= 0\n }\n }\n}\n\nexport function evaluateCondition(condition: Condition, env: ConditionEnv): boolean {\n switch (condition.type) {\n case 'trait':\n return evaluateTrait(env.identity.traits[condition.key], condition.op, condition.value)\n case 'route':\n return env.route !== undefined && matchRoute(condition.pattern, env.route)\n case 'element': {\n const exists = env.elementExists?.(condition.target) ?? false\n return exists === (condition.exists ?? true)\n }\n case 'tour':\n return (env.tourState?.(condition.id) ?? 'not-started') === condition.state\n case 'all':\n return condition.conditions.every((c) => evaluateCondition(c, env))\n case 'any':\n return condition.conditions.some((c) => evaluateCondition(c, env))\n case 'not':\n return !evaluateCondition(condition.condition, env)\n case 'custom':\n return env.custom?.[condition.name]?.(condition.args) ?? false\n }\n}\n\n/** All conditions must hold. An empty or missing list holds. */\nexport function evaluateAll(conditions: Condition[] | undefined, env: ConditionEnv): boolean {\n return (conditions ?? []).every((c) => evaluateCondition(c, env))\n}\n","/**\n * Extension seams — the interfaces the runtime is built against so that tours,\n * user identity, storage and analytics can come from anywhere: inline JSON,\n * a local file, the user's own backend, or a hosted service.\n */\n\nimport type { Tour, TraitValue } from './schema/tour'\n\nexport type MaybePromise<T> = T | Promise<T>\n\n// ---------------------------------------------------------------------------\n// Tour source\n// ---------------------------------------------------------------------------\n\nexport interface TourSource {\n /** Return every tour this source knows about. */\n load(): MaybePromise<Tour[]>\n /**\n * Optional live updates. Return an unsubscribe function.\n * Lets a hosted source push tour changes without a page reload.\n */\n subscribe?(listener: (tours: Tour[]) => void): () => void\n}\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\nexport interface Identity {\n /** Stable user id. Omit for anonymous visitors. */\n id?: string\n /** Attributes that `trait` conditions evaluate against. */\n traits: Record<string, TraitValue>\n}\n\nexport const ANONYMOUS_IDENTITY: Identity = { traits: {} }\n\n// ---------------------------------------------------------------------------\n// Storage\n// ---------------------------------------------------------------------------\n\n/**\n * Key/value storage for progress and seen-state. Shape matches `localStorage`\n * but every method may be async so React Native's AsyncStorage fits too.\n */\nexport interface StorageAdapter {\n get(key: string): MaybePromise<string | null>\n set(key: string, value: string): MaybePromise<void>\n remove(key: string): MaybePromise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DocentEventType =\n | 'tour:started'\n | 'tour:completed'\n | 'tour:skipped'\n | 'tour:aborted'\n | 'step:shown'\n | 'step:completed'\n | 'step:skipped'\n | 'step:missing'\n\nexport interface DocentEvent {\n type: DocentEventType\n tourId: string\n tourVersion: number\n stepId?: string\n stepIndex?: number\n /** Unix epoch milliseconds. */\n timestamp: number\n identity: Identity\n}\n\n/** Receives every lifecycle event. Wire it to console, your analytics, or a hosted endpoint. */\nexport interface EventSink {\n emit(event: DocentEvent): void\n}\n","/**\n * Persisted per-tour progress: what the user has seen, where they stopped,\n * and whether the tour should be offered again.\n */\n\nimport type { Frequency, Tour, TourProgressState } from '../schema/tour'\nimport type { StorageAdapter } from '../seams'\n\nexport interface TourRecord {\n tourId: string\n version: number\n state: TourProgressState\n /** Step to resume from when the tour was interrupted. */\n stepId?: string\n /** Unix epoch milliseconds. */\n updatedAt: number\n}\n\nexport const STORAGE_PREFIX = 'docent:'\n\nexport function storageKey(tourId: string): string {\n return `${STORAGE_PREFIX}${tourId}`\n}\n\nexport function tourVersion(tour: Tour): number {\n return tour.version ?? 1\n}\n\n/** Whether a tour should be offered given what the user has already done. */\nexport function shouldShow(tour: Tour, record: TourRecord | null): boolean {\n if (!record) return true\n if (record.version < tourVersion(tour)) return true\n const frequency: Frequency = tour.options?.frequency ?? 'once'\n switch (frequency) {\n case 'always':\n return true\n case 'until-completed':\n return record.state !== 'completed'\n case 'once':\n return record.state === 'not-started' || record.state === 'in-progress'\n }\n}\n\nexport class ProgressStore {\n constructor(private readonly storage: StorageAdapter) {}\n\n async get(tourId: string): Promise<TourRecord | null> {\n const raw = await this.storage.get(storageKey(tourId))\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<TourRecord>\n if (parsed.tourId !== tourId || typeof parsed.version !== 'number' || !parsed.state) {\n return null\n }\n return parsed as TourRecord\n } catch {\n return null\n }\n }\n\n async set(record: TourRecord): Promise<void> {\n await this.storage.set(storageKey(record.tourId), JSON.stringify(record))\n }\n\n async clear(tourId: string): Promise<void> {\n await this.storage.remove(storageKey(tourId))\n }\n}\n\n/** In-memory adapter. Default when no storage is configured, and handy in tests. */\nexport function createMemoryStorage(): StorageAdapter {\n const map = new Map<string, string>()\n return {\n get: (key) => map.get(key) ?? null,\n set: (key, value) => {\n map.set(key, value)\n },\n remove: (key) => {\n map.delete(key)\n },\n }\n}\n","import type { Tour } from '../schema/tour'\nimport type { DocentEvent, DocentEventType, EventSink, Identity } from '../seams'\nimport { tourVersion } from './progress'\n\nexport interface EventInput {\n tour: Tour\n identity: Identity\n stepIndex?: number\n now?: () => number\n}\n\nexport function createEvent(type: DocentEventType, input: EventInput): DocentEvent {\n const event: DocentEvent = {\n type,\n tourId: input.tour.id,\n tourVersion: tourVersion(input.tour),\n timestamp: (input.now ?? Date.now)(),\n identity: input.identity,\n }\n if (input.stepIndex !== undefined && input.stepIndex >= 0) {\n const step = input.tour.steps[input.stepIndex]\n event.stepIndex = input.stepIndex\n if (step) event.stepId = step.id\n }\n return event\n}\n\n/** Sink that drops everything. Default when none is configured. */\nexport const NOOP_SINK: EventSink = { emit() {} }\n\n/** Fan out to several sinks. */\nexport function combineSinks(...sinks: EventSink[]): EventSink {\n return {\n emit: (e) => {\n for (const s of sinks) s.emit(e)\n },\n }\n}\n","/**\n * Pure state machine for a single running tour. No side effects, no timers,\n * no DOM. Eligibility of steps (conditions, missing targets) is supplied by\n * the caller through {@link EngineContext} so the reducer stays pure.\n */\n\nimport type { Tour } from '../schema/tour'\n\nexport type TourStatus = 'idle' | 'running' | 'paused' | 'completed' | 'skipped' | 'aborted'\n\nexport interface EngineState {\n status: TourStatus\n /** Index into `tour.steps`, or -1 when not running. */\n index: number\n /** Indices of previously shown steps, oldest first. Drives `back`. */\n history: number[]\n /** Set when status is `aborted` or `paused`. */\n reason?: string\n}\n\nexport type EngineAction =\n | { type: 'start'; at?: number | string }\n | { type: 'next' }\n | { type: 'back' }\n | { type: 'go'; to: number | string }\n | { type: 'skip' }\n | { type: 'complete' }\n | { type: 'abort'; reason: string }\n | { type: 'pause'; reason: string }\n | { type: 'resume' }\n | { type: 'stepMissing' }\n | { type: 'stepSkipped' }\n\nexport interface EngineContext {\n tour: Tour\n /** Whether the step at `index` may be shown right now. */\n isEligible: (index: number) => boolean\n}\n\nexport const IDLE_STATE: EngineState = Object.freeze({ status: 'idle', index: -1, history: [] })\n\nexport function resolveStepIndex(tour: Tour, ref: number | string): number {\n if (typeof ref === 'number') return ref >= 0 && ref < tour.steps.length ? ref : -1\n return tour.steps.findIndex((s) => s.id === ref)\n}\n\n/** First eligible index at or after `from` (or at or before, when `dir` is -1). -1 if none. */\nexport function findEligible(ctx: EngineContext, from: number, dir: 1 | -1 = 1): number {\n const total = ctx.tour.steps.length\n for (let i = from; i >= 0 && i < total; i += dir) {\n if (ctx.isEligible(i)) return i\n }\n return -1\n}\n\nfunction advance(state: EngineState, ctx: EngineContext, record: boolean): EngineState {\n const next = findEligible(ctx, state.index + 1)\n if (next === -1) return { status: 'completed', index: state.index, history: state.history }\n return {\n status: 'running',\n index: next,\n history: record ? [...state.history, state.index] : state.history,\n }\n}\n\nexport function reduce(state: EngineState, action: EngineAction, ctx: EngineContext): EngineState {\n switch (action.type) {\n case 'start': {\n const at = action.at === undefined ? 0 : resolveStepIndex(ctx.tour, action.at)\n if (at === -1) return { status: 'aborted', index: -1, history: [], reason: 'unknown-step' }\n const first = findEligible(ctx, at)\n if (first === -1) {\n return { status: 'aborted', index: -1, history: [], reason: 'no-eligible-steps' }\n }\n return { status: 'running', index: first, history: [] }\n }\n\n case 'next':\n if (state.status !== 'running') return state\n return advance(state, ctx, true)\n\n case 'stepMissing': {\n if (state.status !== 'running') return state\n const step = ctx.tour.steps[state.index]\n if (step?.onMissing === 'abort') {\n return { ...state, status: 'aborted', reason: 'target-missing' }\n }\n // 'skip' and 'wait' (after the wait elapsed) both move on without recording history.\n return advance(state, ctx, false)\n }\n\n case 'stepSkipped':\n if (state.status !== 'running') return state\n return advance(state, ctx, false)\n\n case 'back': {\n if (state.status !== 'running') return state\n const history = [...state.history]\n let prev = history.pop()\n // Skip over history entries that are no longer eligible.\n while (prev !== undefined && !ctx.isEligible(prev)) prev = history.pop()\n if (prev === undefined) return state\n return { status: 'running', index: prev, history }\n }\n\n case 'go': {\n if (state.status !== 'running') return state\n const to = resolveStepIndex(ctx.tour, action.to)\n if (to === -1 || to === state.index || !ctx.isEligible(to)) return state\n return { status: 'running', index: to, history: [...state.history, state.index] }\n }\n\n case 'pause':\n if (state.status !== 'running') return state\n return { ...state, status: 'paused', reason: action.reason }\n\n case 'resume': {\n if (state.status !== 'paused') return state\n const { reason: _reason, ...rest } = state\n return { ...rest, status: 'running' }\n }\n\n case 'skip':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'skipped', index: state.index, history: state.history }\n\n case 'complete':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'completed', index: state.index, history: state.history }\n\n case 'abort':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { ...state, status: 'aborted', reason: action.reason }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Selectors\n// ---------------------------------------------------------------------------\n\nexport function isActive(state: EngineState): boolean {\n return state.status === 'running' || state.status === 'paused'\n}\n\nexport function isFinished(state: EngineState): boolean {\n return state.status === 'completed' || state.status === 'skipped' || state.status === 'aborted'\n}\n\nexport function canGoBack(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && state.history.some((i) => ctx.isEligible(i))\n}\n\n/** True when another eligible step follows. False on the last step. */\nexport function hasNext(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && findEligible(ctx, state.index + 1) !== -1\n}\n\nexport interface Progress {\n /** 1-based position of the current step. */\n current: number\n total: number\n}\n\n/** Position over all steps, ineligible ones included, so numbers stay stable. */\nexport function progress(state: EngineState, ctx: EngineContext): Progress {\n return { current: state.index + 1, total: ctx.tour.steps.length }\n}\n","/**\n * Orchestrates one tour: drives the pure reducer, talks to the renderer,\n * runs hooks, persists progress and emits events. This is the only stateful,\n * side-effectful piece of the core.\n */\n\nimport type { StepContext, TourHooks } from '../hooks'\nimport type { Step, Tour, TourProgressState } from '../schema/tour'\nimport { ANONYMOUS_IDENTITY, type EventSink, type Identity, type StorageAdapter } from '../seams'\nimport { type ConditionEnv, type CustomPredicate, evaluateCondition } from './conditions'\nimport { createEvent, NOOP_SINK } from './events'\nimport { createMemoryStorage, ProgressStore, type TourRecord, tourVersion } from './progress'\nimport {\n canGoBack,\n type EngineAction,\n type EngineContext,\n type EngineState,\n hasNext,\n IDLE_STATE,\n isFinished,\n progress,\n reduce,\n} from './reducer'\nimport type { RenderContext, Renderer } from './renderer'\nimport { matchRoute } from './route'\n\nexport interface ControllerOptions {\n tour: Tour\n renderer: Renderer\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n hooks?: TourHooks\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n /** Progress of other tours, for `tour` conditions. */\n tourState?: (tourId: string) => TourProgressState\n /** How long `onMissing: 'wait'` waits when the step sets no `waitFor`. */\n defaultWaitMs?: number\n now?: () => number\n}\n\nexport type StateListener = (state: EngineState) => void\n\nconst DEFAULT_WAIT_MS = 3000\n\nexport class TourController {\n tour: Tour\n private state: EngineState = IDLE_STATE\n private readonly renderer: Renderer\n private readonly identity: Identity\n private readonly store: ProgressStore\n private readonly sink: EventSink\n private readonly hooks: TourHooks\n private readonly custom: Record<string, CustomPredicate>\n private readonly tourStateOf: ((tourId: string) => TourProgressState) | undefined\n private readonly defaultWaitMs: number\n private readonly now: () => number\n private readonly listeners = new Set<StateListener>()\n\n /** Bumped whenever an async flow must be abandoned. */\n private generation = 0\n private pendingAbort: AbortController | undefined\n private pendingTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(options: ControllerOptions) {\n this.tour = options.tour\n this.renderer = options.renderer\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.store = new ProgressStore(options.storage ?? createMemoryStorage())\n this.sink = options.sink ?? NOOP_SINK\n this.hooks = options.hooks ?? {}\n this.custom = options.custom ?? {}\n this.tourStateOf = options.tourState\n this.defaultWaitMs = options.defaultWaitMs ?? DEFAULT_WAIT_MS\n this.now = options.now ?? Date.now\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n getState(): EngineState {\n return this.state\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Start from the first eligible step, or from `at` (step id or index). */\n async start(at?: number | string): Promise<void> {\n if (this.state.status === 'running' || this.state.status === 'paused') return\n this.cancelPending()\n this.dispatch(at === undefined ? { type: 'start' } : { type: 'start', at })\n if (isFinished(this.state)) return this.finish()\n this.emit('tour:started')\n this.hooks.onStart?.(this.tour)\n await this.persist('in-progress')\n await this.showCurrent()\n }\n\n /** Start where the user left off, according to persisted progress. */\n async resume(): Promise<void> {\n const record = await this.store.get(this.tour.id)\n if (record?.state === 'in-progress' && record.version === tourVersion(this.tour)) {\n return this.start(record.stepId)\n }\n return this.start()\n }\n\n async next(): Promise<void> {\n if (this.state.status !== 'running') return\n await this.leaveCurrent()\n this.emit('step:completed', this.state.index)\n this.dispatch({ type: 'next' })\n await this.afterTransition()\n }\n\n async back(): Promise<void> {\n if (!canGoBack(this.state, this.context())) return\n await this.leaveCurrent()\n this.dispatch({ type: 'back' })\n await this.afterTransition()\n }\n\n async goTo(step: number | string): Promise<void> {\n if (this.state.status !== 'running') return\n const before = this.state\n const after = reduce(before, { type: 'go', to: step }, this.context())\n if (after === before) return\n await this.leaveCurrent()\n this.setState(after)\n await this.afterTransition()\n }\n\n /** The user gave up on the tour (Skip button, close, Escape). */\n async skip(): Promise<void> {\n if (!this.isActive()) return\n const ctx = this.stepContext()\n await this.leaveCurrent()\n this.dispatch({ type: 'skip' })\n await this.finish()\n if (ctx) this.hooks.onSkip?.(ctx)\n }\n\n async abort(reason: string): Promise<void> {\n if (!this.isActive()) return\n await this.leaveCurrent()\n this.dispatch({ type: 'abort', reason })\n await this.finish()\n }\n\n /**\n * Swap in a new definition of this tour (live editing). A running tour\n * re-renders its current step, or the nearest one if that step was removed.\n */\n async updateTour(tour: Tour): Promise<void> {\n const currentId = this.currentStep()?.id\n this.tour = tour\n if (!this.isActive()) return\n let index = currentId === undefined ? -1 : tour.steps.findIndex((s) => s.id === currentId)\n if (index === -1) index = Math.min(this.state.index, tour.steps.length - 1)\n if (index < 0) {\n await this.abort('tour-emptied')\n return\n }\n const history = this.state.history.filter((i) => i < tour.steps.length && i !== index)\n this.setState({ ...this.state, index, history })\n if (this.state.status === 'running') await this.showCurrent()\n }\n\n /** Report a named application event. Advances a step waiting on it. */\n notify(eventName: string): void {\n const step = this.currentStep()\n const advance = step?.advance\n if (typeof advance === 'object' && advance.on === 'event' && advance.name === eventName) {\n void this.next()\n }\n }\n\n /** Tell the controller the route changed. Pauses or resumes route-bound steps. */\n async routeChanged(): Promise<void> {\n const step = this.currentStep()\n if (!step) return\n const onRoute = this.stepOnRoute(step)\n if (this.state.status === 'paused' && this.state.reason === 'route' && onRoute) {\n this.dispatch({ type: 'resume' })\n await this.showCurrent()\n } else if (this.state.status === 'running' && !onRoute) {\n this.cancelPending()\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n }\n }\n\n /** Stop everything and clear the screen without recording an outcome. */\n async destroy(): Promise<void> {\n this.cancelPending()\n this.listeners.clear()\n // Reset before awaiting: a start() that runs while hide() settles must not\n // be overwritten afterwards (React StrictMode destroys, then reuses).\n const hidden = this.renderer.hide()\n this.state = IDLE_STATE\n await hidden\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isActive(): boolean {\n return this.state.status === 'running' || this.state.status === 'paused'\n }\n\n private currentStep(): Step | undefined {\n return this.isActive() ? this.tour.steps[this.state.index] : undefined\n }\n\n private conditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.renderer.hasTarget(t),\n custom: this.custom,\n }\n const route = this.renderer.currentRoute?.()\n if (route !== undefined) env.route = route\n if (this.tourStateOf) env.tourState = this.tourStateOf\n return env\n }\n\n private context(): EngineContext {\n const env = this.conditionEnv()\n return {\n tour: this.tour,\n isEligible: (i) => {\n const step = this.tour.steps[i]\n if (!step) return false\n return step.condition ? evaluateCondition(step.condition, env) : true\n },\n }\n }\n\n private stepOnRoute(step: Step): boolean {\n const route = this.renderer.currentRoute?.()\n if (!step.route || route === undefined) return true\n return matchRoute(step.route, route)\n }\n\n private dispatch(action: EngineAction): void {\n this.setState(reduce(this.state, action, this.context()))\n }\n\n private setState(next: EngineState): void {\n if (next === this.state) return\n this.state = next\n for (const l of this.listeners) l(next)\n }\n\n private stepContext(): StepContext | undefined {\n const step = this.currentStep()\n if (!step) return undefined\n return { tour: this.tour, step, index: this.state.index, total: this.tour.steps.length }\n }\n\n private emit(type: Parameters<typeof createEvent>[0], stepIndex?: number): void {\n const input: Parameters<typeof createEvent>[1] = {\n tour: this.tour,\n identity: this.identity,\n now: this.now,\n }\n if (stepIndex !== undefined) input.stepIndex = stepIndex\n this.sink.emit(createEvent(type, input))\n }\n\n private async persist(state: TourProgressState): Promise<void> {\n const record: TourRecord = {\n tourId: this.tour.id,\n version: tourVersion(this.tour),\n state,\n updatedAt: this.now(),\n }\n const step = this.currentStep()\n if (state === 'in-progress' && step) record.stepId = step.id\n await this.store.set(record)\n }\n\n private cancelPending(): void {\n this.generation++\n this.pendingAbort?.abort()\n this.pendingAbort = undefined\n if (this.pendingTimer !== undefined) clearTimeout(this.pendingTimer)\n this.pendingTimer = undefined\n }\n\n /** Run `beforeHide` for the step being left, if any. */\n private async leaveCurrent(): Promise<void> {\n const ctx = this.stepContext()\n this.cancelPending()\n if (ctx && this.state.status === 'running') {\n await this.hooks.steps?.[ctx.step.id]?.beforeHide?.(ctx)\n }\n }\n\n private async afterTransition(): Promise<void> {\n if (this.state.status === 'running') return this.showCurrent()\n if (isFinished(this.state)) return this.finish()\n }\n\n private async finish(): Promise<void> {\n this.cancelPending()\n await this.renderer.hide()\n switch (this.state.status) {\n case 'completed':\n this.emit('tour:completed')\n await this.persist('completed')\n this.hooks.onComplete?.(this.tour)\n break\n case 'skipped':\n this.emit('tour:skipped')\n await this.persist('skipped')\n break\n case 'aborted':\n this.emit('tour:aborted')\n await this.persist('skipped')\n this.hooks.onAbort?.(this.tour, this.state.reason ?? 'unknown')\n break\n }\n }\n\n private async showCurrent(): Promise<void> {\n this.cancelPending()\n const generation = this.generation\n const stale = () => generation !== this.generation\n const ctx = this.stepContext()\n if (!ctx) return\n const { step } = ctx\n\n if (!this.stepOnRoute(step)) {\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n return\n }\n\n const proceed = await this.hooks.steps?.[step.id]?.beforeShow?.(ctx)\n if (stale()) return\n if (proceed === false) {\n this.emit('step:skipped', this.state.index)\n this.dispatch({ type: 'stepSkipped' })\n return this.afterTransition()\n }\n\n if (step.target !== undefined && !(await this.ensureTarget(step, generation))) {\n if (stale()) return\n this.emit('step:missing', this.state.index)\n this.dispatch({ type: 'stepMissing' })\n return this.afterTransition()\n }\n if (stale()) return\n\n await this.renderer.show(this.renderContext(ctx))\n if (stale()) return\n this.emit('step:shown', this.state.index)\n this.hooks.onStepChange?.(ctx)\n await this.persist('in-progress')\n await this.hooks.steps?.[step.id]?.afterShow?.(ctx)\n if (stale()) return\n this.armAdvance(step, generation)\n }\n\n /** Resolve `true` when the target is present, waiting if the step allows it. */\n private async ensureTarget(step: Step, generation: number): Promise<boolean> {\n const target = step.target\n if (target === undefined) return true\n if (this.renderer.hasTarget(target)) return true\n const waitMs = step.waitFor ?? (step.onMissing === 'wait' ? this.defaultWaitMs : 0)\n if (waitMs <= 0) return false\n const abort = new AbortController()\n this.pendingAbort = abort\n const found = await this.renderer.waitForTarget(target, waitMs, abort.signal)\n if (generation !== this.generation) return false\n this.pendingAbort = undefined\n return found\n }\n\n /** Set up automatic advancement for `delay` and `element` steps. */\n private armAdvance(step: Step, generation: number): void {\n const advance = step.advance\n if (typeof advance !== 'object') return\n if (advance.on === 'delay') {\n this.pendingTimer = setTimeout(() => {\n this.pendingTimer = undefined\n if (generation === this.generation) void this.next()\n }, advance.ms)\n } else if (advance.on === 'element') {\n const abort = new AbortController()\n this.pendingAbort = abort\n void this.renderer\n .waitForTarget(advance.target, Number.POSITIVE_INFINITY, abort.signal)\n .then((found) => {\n if (found && generation === this.generation) void this.next()\n })\n }\n }\n\n private renderContext(ctx: StepContext): RenderContext {\n const engineCtx = this.context()\n return {\n tour: this.tour,\n step: ctx.step,\n index: ctx.index,\n progress: progress(this.state, engineCtx),\n isFirst: this.state.history.length === 0,\n isLast: !hasNext(this.state, engineCtx),\n canGoBack: canGoBack(this.state, engineCtx),\n actions: {\n next: () => void this.next(),\n back: () => void this.back(),\n skip: () => void this.skip(),\n goTo: (s) => void this.goTo(s),\n },\n }\n }\n}\n","import type { StorageAdapter } from '../seams'\n\n/**\n * Prefix every key with the user id so progress on a shared browser (or a\n * developer switching test accounts) never leaks between users.\n * Anonymous visitors use the unprefixed keys.\n */\nexport function scopeStorage(base: StorageAdapter, userId: string | undefined): StorageAdapter {\n if (!userId) return base\n const prefix = `u:${userId}:`\n return {\n get: (key) => base.get(prefix + key),\n set: (key, value) => base.set(prefix + key, value),\n remove: (key) => base.remove(prefix + key),\n }\n}\n","/**\n * The tour manager. Holds many tours, watches their triggers, checks\n * conditions and frequency, and starts at most one at a time. This is what\n * turns the rules in the tour JSON into behaviour.\n */\n\nimport { type ConditionEnv, type CustomPredicate, evaluateAll } from '../engine/conditions'\nimport type { ControllerOptions, TourController } from '../engine/controller'\nimport {\n createMemoryStorage,\n ProgressStore,\n shouldShow,\n type TourRecord,\n tourVersion,\n} from '../engine/progress'\nimport { matchRoute } from '../engine/route'\nimport type { TourHooks } from '../hooks'\nimport type { Tour, TourProgressState, TraitValue, Trigger } from '../schema/tour'\nimport {\n ANONYMOUS_IDENTITY,\n type DocentEvent,\n type EventSink,\n type Identity,\n type StorageAdapter,\n type TourSource,\n} from '../seams'\nimport type { DocentEnvironment } from './environment'\nimport { scopeStorage } from './scoped-storage'\n\n/** Options every controller the manager creates receives. */\nexport type SharedControllerOptions = Omit<ControllerOptions, 'tour' | 'renderer'>\n\nexport interface DocentOptions {\n /** Tours to manage: an array, or a source that loads (and may live-update) them. */\n tours?: Tour[] | TourSource\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n /** Hooks per tour, keyed by tour id. */\n hooks?: Record<string, TourHooks>\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n environment: DocentEnvironment\n /** Builds a platform controller for a tour. The DOM package supplies this. */\n createController: (tour: Tour, options: SharedControllerOptions) => TourController\n now?: () => number\n /**\n * Start watching triggers right away. Default true. Framework bindings pass\n * `false` and call `connect()` / `disconnect()` from their mount lifecycle,\n * which keeps construction free of side effects (React StrictMode).\n */\n connect?: boolean\n}\n\nexport interface DocentState {\n /** Id of the tour currently running or paused, if any. */\n active: string | null\n /** Tours known to the manager. */\n tours: string[]\n}\n\nexport type DocentListener = (state: DocentState) => void\n\nexport interface StartOptions {\n /** Step id or index to start from. */\n at?: number | string\n}\n\ntype Cleanup = () => void\n\nfunction isTourSource(value: Tour[] | TourSource | undefined): value is TourSource {\n return !!value && !Array.isArray(value) && typeof (value as TourSource).load === 'function'\n}\n\nexport class Docent {\n private readonly options: DocentOptions\n private readonly env: DocentEnvironment\n private readonly baseStorage: StorageAdapter\n private identity: Identity\n private store: ProgressStore\n private tours = new Map<string, Tour>()\n private records = new Map<string, TourRecord | null>()\n private controller: TourController | undefined\n private activeId: string | null = null\n /** Tours whose trigger fired while another tour was running. */\n private queue: string[] = []\n private triggerCleanups: Cleanup[] = []\n /** `auto` triggers fire once per manager instance (per page load), not on every re-arm. */\n private readonly autoFired = new Set<string>()\n private readonly cleanups: Cleanup[] = []\n private readonly timers = new Set<ReturnType<typeof setTimeout>>()\n private readonly listeners = new Set<DocentListener>()\n private readonly eventListeners = new Set<(event: DocentEvent) => void>()\n private destroyed = false\n private loading: Promise<void> | undefined\n /** Wanted by the owner (between connect and disconnect). */\n private connected = false\n /** Listening to routes, the source and triggers. */\n private attached = false\n\n constructor(options: DocentOptions) {\n this.options = options\n this.env = options.environment\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.baseStorage = options.storage ?? createMemoryStorage()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, this.identity.id))\n if (options.connect !== false) this.connect()\n }\n\n /** Resolves once tours and progress are loaded. Loading starts on first use. */\n get ready(): Promise<void> {\n this.loading ??= this.load()\n return this.loading\n }\n\n /** Start watching routes, the tour source and triggers. Idempotent. */\n connect(): void {\n if (this.destroyed || this.connected) return\n this.connected = true\n void this.ready.then(() => {\n if (this.connected && !this.attached && !this.destroyed) this.attach()\n })\n }\n\n /**\n * Stop watching and remove any running tour without recording an outcome.\n * `connect()` resumes. Unlike `destroy()`, the manager stays usable.\n */\n async disconnect(): Promise<void> {\n this.connected = false\n if (this.attached) {\n this.attached = false\n this.disarmTriggers()\n for (const c of this.cleanups) c()\n this.cleanups.length = 0\n }\n this.queue = []\n await this.stopActive()\n this.emitState()\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** The tours currently managed. */\n getTours(): Tour[] {\n return [...this.tours.values()]\n }\n\n /**\n * Observe every lifecycle event from every tour, in addition to the `sink`\n * option. Returns an unsubscribe function. Used by devtools.\n */\n onEvent(listener: (event: DocentEvent) => void): () => void {\n this.eventListeners.add(listener)\n return () => this.eventListeners.delete(listener)\n }\n\n getState(): DocentState {\n return { active: this.activeId, tours: [...this.tours.keys()] }\n }\n\n subscribe(listener: DocentListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** The controller of the running tour, for fine-grained control. */\n get activeController(): TourController | undefined {\n return this.controller\n }\n\n /**\n * Set who the user is. Progress is stored per user id, and traits feed\n * `trait` conditions. Re-evaluates triggers, since the user may now qualify.\n */\n async identify(id: string | undefined, traits: Record<string, TraitValue> = {}): Promise<void> {\n await this.ready\n const userChanged = id !== this.identity.id\n this.identity = id === undefined ? { traits } : { id, traits }\n if (userChanged) {\n this.autoFired.clear()\n this.store = new ProgressStore(scopeStorage(this.baseStorage, id))\n await this.loadRecords()\n }\n this.armTriggers()\n }\n\n /**\n * Report something that happened in your app. Starts tours with a matching\n * `event` trigger and advances a running step waiting on that event.\n */\n track(eventName: string): void {\n this.controller?.notify(eventName)\n for (const tour of this.tours.values()) {\n const t = tour.trigger\n if (t?.type === 'event' && t.name === eventName) this.fire(tour.id)\n }\n }\n\n /**\n * Start a tour now, ignoring its trigger, conditions and frequency. Use for\n * \"take the tour\" buttons. Stops any tour already running.\n */\n async start(tourId: string, options: StartOptions = {}): Promise<boolean> {\n await this.ready\n const tour = this.tours.get(tourId)\n if (!tour) return false\n await this.stopActive()\n await this.run(tour, options.at, true)\n return true\n }\n\n /** Whether a tour's conditions hold and its frequency allows showing it now. */\n isEligible(tourId: string): boolean {\n const tour = this.tours.get(tourId)\n if (!tour) return false\n return (\n shouldShow(tour, this.records.get(tourId) ?? null) &&\n evaluateAll(tour.conditions, this.getConditionEnv())\n )\n }\n\n /** Progress state of a tour for the current user. */\n tourState(tourId: string): TourProgressState {\n const tour = this.tours.get(tourId)\n const record = this.records.get(tourId)\n if (!record) return 'not-started'\n if (tour && record.version < tourVersion(tour)) return 'not-started'\n return record.state\n }\n\n /**\n * Replace a tour's definition, e.g. from a live editor. If it is running, the\n * current step re-renders with the new content. Triggers are not re-armed, so\n * editing never starts a tour by itself.\n */\n async updateTour(tour: Tour): Promise<void> {\n await this.ready\n this.tours.set(tour.id, tour)\n if (this.activeId === tour.id) await this.controller?.updateTour(tour)\n this.emitState()\n }\n\n /** Forget progress for one tour, or all of them, so they show again. */\n async reset(tourId?: string): Promise<void> {\n await this.ready\n const ids = tourId ? [tourId] : [...this.tours.keys()]\n for (const id of ids) {\n await this.store.clear(id)\n this.records.set(id, null)\n this.autoFired.delete(id)\n }\n this.armTriggers()\n }\n\n /**\n * Re-check triggers and tell the running tour the route may have changed.\n * Call after navigation if your router does not emit browser navigation events.\n */\n async refresh(): Promise<void> {\n await this.ready\n this.armTriggers()\n await this.controller?.routeChanged()\n }\n\n /** Stop the running tour (recorded as skipped). */\n async stop(): Promise<void> {\n await this.controller?.skip()\n }\n\n async destroy(): Promise<void> {\n await this.disconnect()\n this.destroyed = true\n this.listeners.clear()\n this.eventListeners.clear()\n }\n\n // -------------------------------------------------------------------------\n // Loading\n // -------------------------------------------------------------------------\n\n private async load(): Promise<void> {\n const source = this.options.tours\n const initial = isTourSource(source) ? await source.load() : (source ?? [])\n this.setTours(initial)\n await this.loadRecords()\n }\n\n private attach(): void {\n this.attached = true\n const source = this.options.tours\n if (isTourSource(source) && source.subscribe) {\n this.cleanups.push(\n source.subscribe((tours) => {\n this.setTours(tours)\n void this.loadRecords().then(() => this.armTriggers())\n }),\n )\n }\n if (this.env.onRouteChange) {\n this.cleanups.push(this.env.onRouteChange(() => this.armTriggers()))\n }\n this.armTriggers()\n }\n\n private setTours(tours: Tour[]): void {\n this.tours = new Map(tours.map((t) => [t.id, t]))\n this.queue = this.queue.filter((id) => this.tours.has(id))\n this.emitState()\n }\n\n private async loadRecords(): Promise<void> {\n const entries = await Promise.all(\n [...this.tours.keys()].map(async (id) => [id, await this.store.get(id)] as const),\n )\n this.records = new Map(entries)\n }\n\n // -------------------------------------------------------------------------\n // Triggers\n // -------------------------------------------------------------------------\n\n private disarmTriggers(): void {\n for (const c of this.triggerCleanups) c()\n this.triggerCleanups = []\n for (const t of this.timers) clearTimeout(t)\n this.timers.clear()\n }\n\n /**\n * (Re)arm every trigger. Cheap; called on load, identify, route change,\n * source updates and after a tour finishes (so chained tours can start).\n * `except` skips one tour, used for the tour that just finished.\n */\n private armTriggers(except?: string): void {\n if (this.destroyed || !this.attached) return\n this.disarmTriggers()\n for (const tour of this.tours.values()) {\n const trigger = tour.trigger\n if (!trigger || tour.id === except) continue\n this.arm(tour, trigger)\n }\n }\n\n private arm(tour: Tour, trigger: Trigger): void {\n switch (trigger.type) {\n case 'manual':\n case 'event':\n return\n case 'auto':\n // Marked as used in fire() only once it actually starts or queues, so a tour\n // that was not eligible at load can still start when the user qualifies.\n if (this.autoFired.has(tour.id)) return\n this.fireAfter(tour.id, trigger.delay)\n return\n case 'route': {\n const route = this.env.currentRoute?.()\n if (route !== undefined && matchRoute(trigger.pattern, route))\n this.fireAfter(tour.id, trigger.delay)\n return\n }\n case 'element': {\n if (!this.env.watchTarget) {\n if (this.env.hasTarget(trigger.target)) this.fireAfter(tour.id, trigger.delay)\n return\n }\n this.triggerCleanups.push(\n this.env.watchTarget(trigger.target, () => this.fireAfter(tour.id, trigger.delay)),\n )\n }\n }\n }\n\n private fireAfter(tourId: string, delay: number | undefined): void {\n if (!delay) {\n this.fire(tourId)\n return\n }\n const timer = setTimeout(() => {\n this.timers.delete(timer)\n this.fire(tourId)\n }, delay)\n this.timers.add(timer)\n }\n\n /** A trigger fired: start the tour if eligible, or queue it behind the running one. */\n private fire(tourId: string): void {\n if (this.destroyed || !this.attached || tourId === this.activeId) return\n if (!this.isEligible(tourId) || !this.triggerStillHolds(tourId)) return\n if (this.tours.get(tourId)?.trigger?.type === 'auto') this.autoFired.add(tourId)\n if (this.activeId) {\n if (!this.queue.includes(tourId)) this.queue.push(tourId)\n return\n }\n const tour = this.tours.get(tourId)\n if (tour) void this.run(tour, undefined, false)\n }\n\n /** Route triggers are only valid while the user is still on a matching route. */\n private triggerStillHolds(tourId: string): boolean {\n const trigger = this.tours.get(tourId)?.trigger\n if (trigger?.type !== 'route') return true\n const route = this.env.currentRoute?.()\n return route !== undefined && matchRoute(trigger.pattern, route)\n }\n\n // -------------------------------------------------------------------------\n // Running\n // -------------------------------------------------------------------------\n\n /** What conditions are evaluated against right now. Used by `isEligible` and devtools. */\n getConditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.env.hasTarget(t),\n tourState: (id) => this.tourState(id),\n custom: this.options.custom ?? {},\n }\n const route = this.env.currentRoute?.()\n if (route !== undefined) env.route = route\n return env\n }\n\n private sharedOptions(tour: Tour): SharedControllerOptions {\n const shared: SharedControllerOptions = {\n identity: this.identity,\n storage: scopeStorage(this.baseStorage, this.identity.id),\n tourState: (id) => this.tourState(id),\n }\n shared.sink = {\n emit: (event) => {\n this.options.sink?.emit(event)\n for (const l of this.eventListeners) l(event)\n },\n }\n const hooks = this.options.hooks?.[tour.id]\n if (hooks) shared.hooks = hooks\n if (this.options.custom) shared.custom = this.options.custom\n if (this.options.now) shared.now = this.options.now\n return shared\n }\n\n private async run(tour: Tour, at: number | string | undefined, manual: boolean): Promise<void> {\n this.queue = this.queue.filter((id) => id !== tour.id)\n const controller = this.options.createController(tour, this.sharedOptions(tour))\n this.controller = controller\n this.activeId = tour.id\n // Mirror what the controller persists, so tourState() is right while it runs.\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state: 'in-progress',\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.emitState()\n\n const off = controller.subscribe((state) => {\n if (\n state.status === 'completed' ||\n state.status === 'skipped' ||\n state.status === 'aborted'\n ) {\n off()\n this.finished(controller, tour, state.status === 'completed' ? 'completed' : 'skipped')\n }\n })\n\n const resume = !manual && at === undefined && tour.options?.persist\n if (resume) await controller.resume()\n else await controller.start(at)\n\n // start() returned without running (nothing happened): release the slot.\n if (controller.getState().status === 'idle' && this.controller === controller) {\n off()\n this.release(controller)\n }\n }\n\n /**\n * The controller emits the final status before it finishes writing storage,\n * so record the outcome here directly instead of reading it back.\n */\n private finished(controller: TourController, tour: Tour, state: 'completed' | 'skipped'): void {\n if (this.controller !== controller) return\n this.records.set(tour.id, {\n tourId: tour.id,\n version: tourVersion(tour),\n state,\n updatedAt: (this.options.now ?? Date.now)(),\n })\n this.release(controller, tour.id)\n }\n\n private release(controller: TourController, finishedId?: string): void {\n this.controller = undefined\n this.activeId = null\n this.emitState()\n // Let the finished controller complete its own cleanup (hide, persist, events) first.\n setTimeout(() => {\n void controller.destroy()\n this.drainQueue()\n // Tours whose conditions depend on the one that just ended may now qualify.\n if (!this.activeId) this.armTriggers(finishedId)\n }, 0)\n }\n\n /** Stop the running tour without recording an outcome (used before a manual start). */\n private async stopActive(): Promise<void> {\n const controller = this.controller\n if (!controller) return\n this.controller = undefined\n this.activeId = null\n await controller.destroy()\n }\n\n private drainQueue(): void {\n while (this.queue.length > 0 && !this.activeId) {\n const next = this.queue.shift()\n if (next) this.fire(next)\n }\n }\n\n private emitState(): void {\n const state = this.getState()\n for (const l of this.listeners) l(state)\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docentjs/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Platform-agnostic tour engine: schema, state machine, triggers, persistence, events. No DOM access.",
5
5
  "license": "MIT",
6
6
  "repository": {