@thelol3882/sapar 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -2
  3. package/dist/adapter-Y9yB3-q3.d.ts +14 -0
  4. package/dist/adapter-fFZPsINL.d.ts +14 -0
  5. package/dist/chunk-7B66QEMY.js +536 -0
  6. package/dist/chunk-7B66QEMY.js.map +1 -0
  7. package/dist/chunk-FM64UDKJ.js +893 -0
  8. package/dist/chunk-FM64UDKJ.js.map +1 -0
  9. package/dist/chunk-VC6XLWM3.js +176 -0
  10. package/dist/chunk-VC6XLWM3.js.map +1 -0
  11. package/dist/chunk-ZBVQGMR4.js +583 -0
  12. package/dist/chunk-ZBVQGMR4.js.map +1 -0
  13. package/dist/cli/index.js +1335 -0
  14. package/dist/core/index.d.ts +273 -0
  15. package/dist/core/index.js +71 -0
  16. package/dist/core/index.js.map +1 -0
  17. package/dist/expo/index.d.ts +189 -0
  18. package/dist/expo/index.js +39 -0
  19. package/dist/expo/index.js.map +1 -0
  20. package/dist/expo-router/index.d.ts +18 -0
  21. package/dist/expo-router/index.js +46 -0
  22. package/dist/expo-router/index.js.map +1 -0
  23. package/dist/next/index.d.ts +18 -0
  24. package/dist/next/index.js +49 -0
  25. package/dist/next/index.js.map +1 -0
  26. package/dist/presentation-CClsPRT6.d.ts +12 -0
  27. package/dist/react/index.d.ts +35 -0
  28. package/dist/react/index.js +29 -0
  29. package/dist/react/index.js.map +1 -0
  30. package/dist/rtk/index.d.ts +751 -0
  31. package/dist/rtk/index.js +114 -0
  32. package/dist/rtk/index.js.map +1 -0
  33. package/dist/stepCount-CTYYFWiV.d.ts +57 -0
  34. package/dist/types-CHD2pB5X.d.ts +192 -0
  35. package/dist/web/index.d.ts +112 -0
  36. package/dist/web/index.js +34 -0
  37. package/dist/web/index.js.map +1 -0
  38. package/package.json +147 -3
  39. package/templates/expo/AnnouncementModal.tsx +102 -0
  40. package/templates/expo/TourCard.tsx +112 -0
  41. package/templates/expo/WhatsNewButton.tsx +87 -0
  42. package/templates/expo/WhatsNewPanel.tsx +167 -0
  43. package/templates/mantine/AnnouncementModal.tsx +66 -0
  44. package/templates/mantine/TourCard.tsx +84 -0
  45. package/templates/mantine/WhatsNewButton.tsx +62 -0
  46. package/templates/mantine/WhatsNewPanel.tsx +129 -0
  47. package/templates/tailwind/AnnouncementModal.tsx +100 -0
  48. package/templates/tailwind/TourCard.tsx +69 -0
  49. package/templates/tailwind/WhatsNewButton.tsx +56 -0
  50. package/templates/tailwind/WhatsNewPanel.tsx +174 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/capping.ts","../src/core/routes.ts","../src/core/steps.ts","../src/core/machine.ts","../src/core/controller.ts","../src/core/define.ts","../src/core/policy.ts","../src/core/stores.ts","../src/core/sources/memory.ts","../src/core/wire.ts","../src/core/sources/http.ts","../src/core/testing.ts","../src/core/spotlightPath.ts","../src/core/presentation.ts"],"sourcesContent":["import type { PlatformAdapter } from './types';\n\nexport const SESSION_CAP_KEY = 'sapar.session-interruption-spent';\n\nexport function hasSpentSessionInterruption(adapter: PlatformAdapter): boolean {\n return adapter.session.get(SESSION_CAP_KEY) !== null;\n}\n\nexport function spendSessionInterruption(adapter: PlatformAdapter): void {\n adapter.session.set(SESSION_CAP_KEY, String(adapter.now()));\n}\n","// Parity port of the backend's routes.py; keep the two in lockstep.\n\nexport function normalizeRoute(route: string | null | undefined, locales: readonly string[]): string {\n const path = (route ?? '').split('#')[0]!.split('?')[0]!.trim();\n const segments = path.split('/').filter((segment) => segment !== '');\n if (segments.length > 0 && locales.includes(segments[0]!)) segments.shift();\n return `/${segments.join('/')}`;\n}\n\nexport function routeMatches(pattern: string, route: string, locales: readonly string[]): boolean {\n const trimmed = pattern.trim();\n if (!pattern) return false;\n if (trimmed === '*') return true;\n\n const target = normalizeRoute(route, locales);\n if (trimmed.endsWith('/*')) {\n const prefix = normalizeRoute(trimmed.slice(0, -2), locales);\n return prefix === '/' || target === prefix || target.startsWith(`${prefix}/`);\n }\n return normalizeRoute(trimmed, locales) === target;\n}\n\nexport function isValidRoutePattern(pattern: string): boolean {\n if (!pattern) return false;\n if (pattern === '*') return true;\n const body = pattern.endsWith('/*') ? pattern.slice(0, -2) : pattern;\n return !body.includes('*');\n}\n","import { normalizeRoute } from './routes';\nimport type { PlatformAdapter, TourStep } from './types';\n\nexport const DEFAULT_ANCHOR_TIMEOUT_MS = 3000;\nexport const DEFAULT_PRESENT_ANCHOR_TIMEOUT_MS = 300;\nexport const DEFAULT_AUTO_START_TIMEOUT_MS = 10_000;\n\nexport function requiresNavigation(\n step: TourStep,\n adapter: PlatformAdapter,\n locales: readonly string[],\n): step is TourStep & { route: string } {\n if (step.route === undefined) return false;\n return normalizeRoute(step.route, locales) !== normalizeRoute(adapter.currentRoute(), locales);\n}\n\n/**\n * Keys of the steps whose anchor is not on the page right now. Steps on another route cannot be told from here\n * and count as present.\n */\nexport async function findMissingAnchors(\n steps: readonly TourStep[],\n adapter: PlatformAdapter,\n locales: readonly string[],\n): Promise<string[]> {\n async function isMissing(step: TourStep): Promise<boolean> {\n if (step.anchor === undefined || requiresNavigation(step, adapter, locales)) return false;\n return (await adapter.resolveAnchor(step.anchor, { timeoutMs: 0 })) === null;\n }\n\n const missing = await Promise.all(steps.map(isMissing));\n return steps.filter((_, index) => missing[index]).map((step) => step.key);\n}\n","import {\n DEFAULT_ANCHOR_TIMEOUT_MS,\n DEFAULT_PRESENT_ANCHOR_TIMEOUT_MS,\n findMissingAnchors,\n requiresNavigation,\n} from './steps';\nimport type { AnchorHandle, PlatformAdapter, ProgressInput, RunnerSnapshot, Tour, TourStatus, TourStep } from './types';\n\nexport interface TourMachineEvents {\n onProgress(input: ProgressInput): void;\n onError(error: unknown, context: string): void;\n}\n\nexport interface TourMachineOptions {\n anchorTimeoutMs?: number;\n presentAnchorTimeoutMs?: number;\n locales?: readonly string[];\n}\n\nexport interface TourMachine {\n subscribe(listener: () => void): () => void;\n /** Referentially stable until the state changes. */\n getSnapshot(): RunnerSnapshot;\n start(tour: Tour, options: { autoStarted: boolean }): Promise<void>;\n next(): Promise<void>;\n previous(): Promise<void>;\n skip(): void;\n finish(): void;\n /** Every method is a no-op afterwards. */\n destroy(): void;\n}\n\nconst IDLE: RunnerSnapshot = { phase: 'idle', tour: null, index: 0, handle: null, skipped: [], autoStarted: false };\n\nconst noop = (): void => {};\n\ntype Direction = 1 | -1;\n\ninterface Arrival {\n kind: 'arrived';\n index: number;\n handle: AnchorHandle | null;\n missed: string[];\n}\n\ntype Seek =\n | Arrival\n | { kind: 'aborted' }\n | { kind: 'exhausted' };\n\ntype Travel = 'landed' | 'exhausted' | 'stopped';\n\nexport function createTourMachine(\n adapter: PlatformAdapter,\n events: TourMachineEvents,\n options: TourMachineOptions = {},\n): TourMachine {\n const anchorTimeoutMs = options.anchorTimeoutMs ?? DEFAULT_ANCHOR_TIMEOUT_MS;\n const presentAnchorTimeoutMs = options.presentAnchorTimeoutMs ?? DEFAULT_PRESENT_ANCHOR_TIMEOUT_MS;\n const locales = options.locales ?? [];\n const listeners = new Set<() => void>();\n let snapshot = IDLE;\n let destroyed = false;\n let stopListeningForPress = noop;\n // A transition captures its token before its first await and drops its result if another bumped it meanwhile.\n let transitionToken = 0;\n\n function beginTransition(): number {\n stopListeningForPress();\n stopListeningForPress = noop;\n return ++transitionToken;\n }\n\n function isCurrent(token: number): boolean {\n return !destroyed && token === transitionToken;\n }\n\n function commit(next: RunnerSnapshot): void {\n snapshot = next;\n stopListeningForPress();\n listenToCurrentStep();\n listeners.forEach((listener) => listener());\n }\n\n function listenToCurrentStep(): void {\n stopListeningForPress = listenForPress(snapshot);\n }\n\n function listenForPress({ phase, tour, index, handle }: RunnerSnapshot): () => void {\n if (phase !== 'running' || tour?.steps[index]?.advanceOn !== 'press' || !handle) return noop;\n return handle.onPress(() => void next());\n }\n\n // Right after a navigation the page is still loading; otherwise it is rendered and a missing anchor stays missing.\n async function resolveTarget(step: TourStep): Promise<{ handle: AnchorHandle | null } | null> {\n let timeoutMs = presentAnchorTimeoutMs;\n if (requiresNavigation(step, adapter, locales)) {\n await adapter.navigate(step.route);\n timeoutMs = anchorTimeoutMs;\n }\n if (step.anchor === undefined) return { handle: null };\n const handle = await adapter.resolveAnchor(step.anchor, { timeoutMs });\n return handle && { handle };\n }\n\n // Returns null when the transition was superseded.\n async function seek(token: number, tour: Tour, from: number, direction: Direction): Promise<Seek | null> {\n const missed: string[] = [];\n for (let index = from; ; index += direction) {\n const step = tour.steps[index];\n if (!step) return { kind: 'exhausted' };\n\n const target = await resolveTarget(step);\n if (!isCurrent(token)) return null;\n if (target) return { kind: 'arrived', index, handle: target.handle, missed };\n if (step.required) return { kind: 'aborted' };\n missed.push(step.key);\n }\n }\n\n async function land(token: number, tour: Tour, arrival: Arrival): Promise<Travel> {\n const { index, handle, missed } = arrival;\n if (handle) {\n await adapter.scrollIntoView(handle, { reducedMotion: adapter.prefersReducedMotion() });\n if (!isCurrent(token)) return 'stopped';\n }\n const missingLater = await findMissingAnchors(tour.steps.slice(index + 1), adapter, locales);\n if (!isCurrent(token)) return 'stopped';\n\n const known = new Set([...snapshot.skipped, ...missed]);\n const missingEarlier = tour.steps\n .slice(0, index)\n .filter((step) => known.has(step.key))\n .map((step) => step.key);\n const skipped = [...missingEarlier, ...missingLater];\n commit({ phase: 'running', tour, index, handle, skipped, autoStarted: snapshot.autoStarted });\n return 'landed';\n }\n\n // A required step that is missing on arrival closes the run without reporting, so the tour can be offered again.\n async function travel(token: number, tour: Tour, from: number, direction: Direction): Promise<Travel> {\n const found = await seek(token, tour, from, direction);\n if (!found) return 'stopped';\n if (found.kind === 'aborted') {\n commit(IDLE);\n return 'stopped';\n }\n return found.kind === 'exhausted' ? 'exhausted' : land(token, tour, found);\n }\n\n // Adapters report a missing anchor as null, so a rejection is an adapter bug: close the tour instead of hanging in 'preparing'.\n async function guarded(token: number, transition: () => Promise<void>): Promise<void> {\n try {\n await transition();\n } catch (error) {\n if (isCurrent(token)) commit(IDLE);\n events.onError(error, 'tour machine');\n }\n }\n\n async function start(tour: Tour, { autoStarted }: { autoStarted: boolean }): Promise<void> {\n if (destroyed) return;\n const token = beginTransition();\n commit({ ...IDLE, phase: 'preparing', tour, autoStarted });\n\n await guarded(token, async () => {\n const outcome = await travel(token, tour, 0, 1);\n if (outcome === 'exhausted') commit(IDLE);\n if (outcome === 'landed') {\n const { key, revision } = tour;\n events.onProgress({ key, revision, status: 'started', stepIndex: snapshot.index, autoStarted });\n }\n });\n }\n\n async function move(direction: Direction): Promise<void> {\n const { phase, tour, index } = snapshot;\n if (destroyed || phase !== 'running' || !tour) return;\n const token = beginTransition();\n\n await guarded(token, async () => {\n const outcome = await travel(token, tour, index + direction, direction);\n if (outcome !== 'exhausted') return;\n if (direction === 1) finish();\n else listenToCurrentStep();\n });\n }\n\n const next = (): Promise<void> => move(1);\n const previous = (): Promise<void> => move(-1);\n\n function end(status: Exclude<TourStatus, 'started'>): void {\n const { phase, tour, index, autoStarted } = snapshot;\n if (destroyed || phase !== 'running' || !tour) return;\n beginTransition();\n commit(IDLE);\n events.onProgress({ key: tour.key, revision: tour.revision, status, stepIndex: index, autoStarted });\n }\n\n const finish = (): void => end('completed');\n const skip = (): void => end('dismissed');\n\n function destroy(): void {\n if (destroyed) return;\n beginTransition();\n destroyed = true;\n listeners.clear();\n }\n\n return {\n subscribe(listener) {\n if (destroyed) return noop;\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n getSnapshot: () => snapshot,\n start,\n next,\n previous,\n skip,\n finish,\n destroy,\n };\n}\n","import { hasSpentSessionInterruption, spendSessionInterruption } from './capping';\nimport { createTourMachine } from './machine';\nimport { normalizeRoute } from './routes';\nimport {\n DEFAULT_ANCHOR_TIMEOUT_MS,\n DEFAULT_AUTO_START_TIMEOUT_MS,\n DEFAULT_PRESENT_ANCHOR_TIMEOUT_MS,\n requiresNavigation,\n} from './steps';\nimport type {\n Inbox,\n Interruption,\n ProgressInput,\n Tour,\n SaparController,\n SaparControllerOptions,\n SaparSnapshot,\n} from './types';\n\nfunction withUnread(inbox: Inbox, keys: ReadonlySet<string>, unread: boolean): Inbox {\n const flipped = inbox.items.filter((item) => keys.has(item.key) && item.unread !== unread);\n if (flipped.length === 0) return inbox;\n\n const countDelta = flipped.filter((item) => item.tier !== 'silent').length * (unread ? 1 : -1);\n return {\n ...inbox,\n unreadCount: inbox.unreadCount + countDelta,\n items: inbox.items.map((item) => (flipped.includes(item) ? { ...item, unread } : item)),\n };\n}\n\nfunction withoutInterruption(inbox: Inbox, announcementKeys: readonly string[]): Inbox {\n const { interruption } = inbox;\n const matches = interruption?.kind === 'announcement' && announcementKeys.includes(interruption.announcement.key);\n return matches ? { ...inbox, interruption: null } : inbox;\n}\n\nexport function createSaparController(options: SaparControllerOptions): SaparController {\n const { source, adapter, locales = [], sessionCap = true } = options;\n const autoStartTimeoutMs = options.autoStartTimeoutMs ?? DEFAULT_AUTO_START_TIMEOUT_MS;\n const onError = options.onError ?? ((error, context) => console.error(`[sapar] ${context}`, error));\n const listeners = new Set<() => void>();\n const autoStartedKeys = new Set<string>();\n let route: string | undefined;\n let inboxRequestId = 0;\n let surfacedAnnouncementKey: string | null = null;\n let destroyed = false;\n\n const machine = createTourMachine(\n adapter,\n { onProgress: handleProgress, onError },\n {\n anchorTimeoutMs: options.anchorTimeoutMs ?? DEFAULT_ANCHOR_TIMEOUT_MS,\n presentAnchorTimeoutMs: options.presentAnchorTimeoutMs ?? DEFAULT_PRESENT_ANCHOR_TIMEOUT_MS,\n locales,\n },\n );\n let snapshot: SaparSnapshot = { inbox: null, inboxStatus: 'idle', runner: machine.getSnapshot() };\n machine.subscribe(() => update({ runner: machine.getSnapshot() }));\n\n function update(patch: Partial<SaparSnapshot>): void {\n const changed = (Object.keys(patch) as Array<keyof SaparSnapshot>).some((name) => patch[name] !== snapshot[name]);\n if (!changed) return;\n snapshot = { ...snapshot, ...patch };\n listeners.forEach((listener) => listener());\n }\n\n // Session cap: once the session's one slot is spent nothing more is offered, except the announcement\n // already on screen. An announcement spends the slot when it first surfaces; a tour when it actually starts.\n function applySessionCap(interruption: Interruption | null): Interruption | null {\n if (!interruption || !sessionCap) return interruption;\n const isSurfaced = interruption.kind === 'announcement' && interruption.announcement.key === surfacedAnnouncementKey;\n if (isSurfaced) return interruption;\n if (hasSpentSessionInterruption(adapter)) return null;\n\n if (interruption.kind === 'announcement') {\n surfacedAnnouncementKey = interruption.announcement.key;\n spendSessionInterruption(adapter);\n }\n return interruption;\n }\n\n // The host renders the first anchor only once its page has loaded, so its arrival means \"ready\".\n async function waitForPage(tour: Tour): Promise<boolean> {\n const first = tour.steps.find((step) => step.anchor !== undefined);\n if (first?.anchor === undefined || requiresNavigation(first, adapter, locales)) return true;\n return (await adapter.resolveAnchor(first.anchor, { timeoutMs: autoStartTimeoutMs })) !== null;\n }\n\n async function autoStart(tour: Tour): Promise<void> {\n if (snapshot.runner.phase !== 'idle' || autoStartedKeys.has(tour.key)) return;\n autoStartedKeys.add(tour.key);\n\n const ready = await waitForPage(tour).catch((error: unknown) => {\n onError(error, 'autoStart');\n return false;\n });\n if (!ready || destroyed || snapshot.runner.phase !== 'idle') {\n autoStartedKeys.delete(tour.key);\n return;\n }\n await machine.start(tour, { autoStarted: true });\n }\n\n function handleProgress(input: ProgressInput): void {\n if (input.status === 'started' && input.autoStarted && sessionCap) spendSessionInterruption(adapter);\n void reportProgress(input);\n }\n\n async function reportProgress(input: ProgressInput): Promise<void> {\n try {\n await source.recordProgress(input);\n } catch (error) {\n onError(error, 'progress');\n return;\n }\n if (input.status !== 'started') await refresh();\n }\n\n async function refresh(): Promise<void> {\n if (destroyed) return;\n const requestId = ++inboxRequestId;\n const query = { route: route === undefined ? undefined : normalizeRoute(route, locales), platform: adapter.platform };\n if (!snapshot.inbox) update({ inboxStatus: 'loading' });\n\n try {\n const inbox = await source.getInbox(query);\n if (destroyed || requestId !== inboxRequestId) return;\n const interruption = applySessionCap(inbox.interruption);\n update({ inbox: { ...inbox, interruption }, inboxStatus: 'ready' });\n if (interruption?.kind === 'tour') void autoStart(interruption.tour);\n } catch (error) {\n if (destroyed || requestId !== inboxRequestId) return;\n update({ inboxStatus: 'error' });\n onError(error, 'inbox');\n }\n }\n\n async function loadTour(tourOrKey: Tour | string): Promise<Tour | null> {\n if (typeof tourOrKey !== 'string') return tourOrKey;\n const tour = await source.getTour(tourOrKey);\n if (!tour) onError(new Error(`No tour with key \"${tourOrKey}\".`), 'startTour');\n return tour;\n }\n\n async function startTour(tourOrKey: Tour | string): Promise<void> {\n try {\n const tour = await loadTour(tourOrKey);\n if (tour && !destroyed) await machine.start(tour, { autoStarted: false });\n } catch (error) {\n onError(error, 'startTour');\n }\n }\n\n async function markSeen(keys: string[]): Promise<void> {\n const flippedKeys = new Set(\n snapshot.inbox?.items.filter((item) => item.unread && keys.includes(item.key)).map((item) => item.key),\n );\n if (snapshot.inbox) update({ inbox: withoutInterruption(withUnread(snapshot.inbox, flippedKeys, false), keys) });\n try {\n await source.markSeen(keys);\n } catch (error) {\n if (snapshot.inbox) update({ inbox: withUnread(snapshot.inbox, flippedKeys, true) });\n onError(error, 'markSeen');\n }\n }\n\n async function dismiss(key: string): Promise<void> {\n if (snapshot.inbox) update({ inbox: withoutInterruption(snapshot.inbox, [key]) });\n try {\n await source.dismiss(key);\n } catch (error) {\n onError(error, 'dismiss');\n return;\n }\n await refresh();\n }\n\n return {\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n getSnapshot: () => snapshot,\n setRoute(nextRoute) {\n route = nextRoute;\n void refresh();\n },\n refresh,\n startTour,\n next: () => machine.next(),\n previous: () => machine.previous(),\n skip: () => machine.skip(),\n markSeen,\n dismiss,\n recordClick(key) {\n source.recordClick(key).catch((error: unknown) => onError(error, 'recordClick'));\n },\n destroy() {\n destroyed = true;\n machine.destroy();\n listeners.clear();\n },\n };\n}\n","import { isValidRoutePattern } from './routes';\nimport type {\n Announcement,\n AnnouncementTier,\n LocalizedText,\n Placement,\n Platform,\n Tour,\n TourStep,\n TourTrigger,\n} from './types';\n\nexport interface StepDefinition {\n key: string;\n anchor?: string;\n route?: string;\n title: LocalizedText;\n body: LocalizedText;\n placement: Placement;\n padding: number;\n required: boolean;\n advanceOn: 'next' | 'press';\n}\n\nexport interface TourDefinition {\n key: string;\n revision: number;\n title: LocalizedText;\n trigger: TourTrigger;\n steps: StepDefinition[];\n /** Platforms the tour is offered on. Absent means all. */\n platforms?: Platform[];\n}\n\nexport interface AnnouncementDefinition {\n key: string;\n tier: AnnouncementTier;\n title: LocalizedText;\n body: LocalizedText;\n imageUrl?: string;\n cta?: { label: LocalizedText; url: string };\n /** ISO 8601. */\n publishedAt: string;\n tourKey?: string;\n}\n\nexport type StepInput = Pick<StepDefinition, 'key' | 'title' | 'body'> & Partial<StepDefinition>;\nexport type TourInput = Pick<TourDefinition, 'key' | 'title'> &\n Partial<Omit<TourDefinition, 'steps'>> & { steps: StepInput[] };\nexport type AnnouncementInput = Pick<AnnouncementDefinition, 'key' | 'title' | 'body' | 'publishedAt'> &\n Partial<AnnouncementDefinition>;\n\nexport interface TextLocale {\n locale: string;\n fallbackLocale: string;\n}\n\n/** Grammar for tour, step, announcement and anchor keys. Shared with the backend and the CLI. */\nexport const KEY_PATTERN = /^[a-z0-9_]+(\\.[a-z0-9_]+)*$/;\n\nfunction assertKey(kind: string, key: string): void {\n if (!KEY_PATTERN.test(key)) {\n throw new Error(`Invalid ${kind} key \"${key}\": use lowercase letters, digits and underscores, dot-separated.`);\n }\n}\n\nfunction assertUnique(kind: string, keys: readonly string[]): void {\n const duplicate = keys.find((key, index) => keys.indexOf(key) !== index);\n if (duplicate !== undefined) throw new Error(`Duplicate ${kind} key \"${duplicate}\".`);\n}\n\nfunction defineStep(input: StepInput, tourKey: string): StepDefinition {\n assertKey(`step in tour \"${tourKey}\"`, input.key);\n if (input.anchor !== undefined) assertKey(`anchor of step \"${input.key}\"`, input.anchor);\n const { placement = 'auto', padding = 8, required = false, advanceOn = 'next', ...rest } = input;\n return { ...rest, placement, padding, required, advanceOn };\n}\n\nexport function defineTour(input: TourInput): TourDefinition {\n assertKey('tour', input.key);\n const { revision = 1, trigger = 'manual' } = input;\n if (!Number.isInteger(revision) || revision < 1) {\n throw new Error(`Tour \"${input.key}\" needs a positive integer revision, got ${revision}.`);\n }\n if (input.steps.length === 0) throw new Error(`Tour \"${input.key}\" needs at least one step.`);\n if (trigger !== 'manual' && !isValidRoutePattern(trigger.route)) {\n throw new Error(`Tour \"${input.key}\" has an invalid route pattern \"${trigger.route}\".`);\n }\n\n const steps = input.steps.map((step) => defineStep(step, input.key));\n assertUnique(`step in tour \"${input.key}\"`, steps.map((step) => step.key));\n return { key: input.key, revision, title: input.title, trigger, steps, ...(input.platforms && { platforms: input.platforms }) };\n}\n\nexport function defineAnnouncement(input: AnnouncementInput): AnnouncementDefinition {\n if (Number.isNaN(Date.parse(input.publishedAt))) {\n throw new Error(`Announcement \"${input.key}\" has an invalid publishedAt \"${input.publishedAt}\".`);\n }\n const { tier = 'feed', ...rest } = input;\n return { ...rest, tier };\n}\n\nexport function defineAnchors<const Keys extends readonly string[]>(keys: Keys): Keys {\n keys.forEach((key) => assertKey('anchor', key));\n assertUnique('anchor', keys);\n return keys;\n}\n\nexport type AnchorKey<Anchors extends readonly string[]> = Anchors[number];\n\nexport function resolveText(text: LocalizedText, locale: string, fallbackLocale: string): string {\n if (typeof text === 'string') return text;\n return text[locale] ?? text[fallbackLocale] ?? '';\n}\n\nfunction resolveStep(step: StepDefinition, { locale, fallbackLocale }: TextLocale): TourStep {\n return {\n ...step,\n title: resolveText(step.title, locale, fallbackLocale),\n body: resolveText(step.body, locale, fallbackLocale),\n };\n}\n\nexport function resolveTour(tour: TourDefinition, textLocale: TextLocale): Tour {\n return {\n ...tour,\n title: resolveText(tour.title, textLocale.locale, textLocale.fallbackLocale),\n steps: tour.steps.map((step) => resolveStep(step, textLocale)),\n };\n}\n\nexport function resolveAnnouncement(\n announcement: AnnouncementDefinition,\n { locale, fallbackLocale }: TextLocale,\n unread: boolean,\n): Announcement {\n const { cta, ...rest } = announcement;\n return {\n ...rest,\n title: resolveText(announcement.title, locale, fallbackLocale),\n body: resolveText(announcement.body, locale, fallbackLocale),\n ...(cta && { cta: { label: resolveText(cta.label, locale, fallbackLocale), url: cta.url } }),\n unread,\n };\n}\n","import { routeMatches } from './routes';\nimport type { Announcement, Interruption, InterruptionPolicy, Tour, TourStatus } from './types';\n\nexport const DEFAULT_POLICY: InterruptionPolicy = {\n newUserGraceDays: 3,\n cooldownHours: 24,\n dismissalBackoffDays: 30,\n};\n\nconst HOUR_MS = 60 * 60 * 1000;\nconst DAY_MS = 24 * HOUR_MS;\n\nexport interface TourRecord {\n status: TourStatus;\n revision: number;\n lastStepIndex: number;\n /** Epoch ms of the first report for this (tour, revision). */\n startedAt: number;\n dismissedAt: number | null;\n /** Sticky: once a run counted as an automatic interruption it stays one. */\n autoStarted: boolean;\n}\n\nexport interface InterruptionBudget {\n policy: InterruptionPolicy;\n now: number;\n registeredAt: number | null;\n lastAutoInterruptionAt: number | null;\n}\n\nfunction isWithin(now: number, since: number | null, windowMs: number): boolean {\n return since !== null && now - since < windowMs;\n}\n\nexport function interruptionsAllowed({ policy, now, registeredAt, lastAutoInterruptionAt }: InterruptionBudget): boolean {\n const inGrace = policy.newUserGraceDays > 0 && isWithin(now, registeredAt, policy.newUserGraceDays * DAY_MS);\n const coolingDown = policy.cooldownHours > 0 && isWithin(now, lastAutoInterruptionAt, policy.cooldownHours * HOUR_MS);\n return !inGrace && !coolingDown;\n}\n\nexport function isTourOfferable({ policy, now, state, revision }: {\n policy: InterruptionPolicy;\n now: number;\n state: TourRecord | undefined;\n revision: number;\n}): boolean {\n if (!state || state.revision !== revision) return true;\n if (state.status === 'completed') return false;\n if (state.status === 'started') return true;\n if (policy.dismissalBackoffDays <= 0) return true;\n return state.dismissedAt !== null && !isWithin(now, state.dismissedAt, policy.dismissalBackoffDays * DAY_MS);\n}\n\nexport interface InterruptionCandidates {\n /** `undefined` means the host sent no route, which opts out of route triggers. */\n route: string | undefined;\n locales: readonly string[];\n tours: readonly Tour[];\n stateOf(tour: Tour): TourRecord | undefined;\n announcements: readonly Announcement[];\n dismissedKeys: ReadonlySet<string>;\n}\n\nfunction pickTour(budget: InterruptionBudget, { route, locales, tours, stateOf }: InterruptionCandidates): Tour | undefined {\n if (route === undefined) return undefined;\n return tours\n .filter(\n (tour) =>\n typeof tour.trigger === 'object' &&\n routeMatches(tour.trigger.route, route, locales) &&\n isTourOfferable({ ...budget, state: stateOf(tour), revision: tour.revision }),\n )\n .sort((a, b) => (a.key < b.key ? -1 : 1))[0];\n}\n\n// A tour beats a modal announcement: the announcement stays in the feed, the tour only fits where the user is now.\nexport function pickInterruption(budget: InterruptionBudget, candidates: InterruptionCandidates): Interruption | null {\n if (!interruptionsAllowed(budget)) return null;\n\n const tour = pickTour(budget, candidates);\n if (tour) return { kind: 'tour', tour };\n\n const announcement = candidates.announcements.find(\n (item) => item.tier === 'modal' && item.unread && !candidates.dismissedKeys.has(item.key),\n );\n return announcement ? { kind: 'announcement', announcement } : null;\n}\n","import type { KeyValueStore } from './types';\n\nexport function memoryStore(): KeyValueStore {\n const values = new Map<string, string>();\n return {\n get: (key) => values.get(key) ?? null,\n set: (key, value) => {\n values.set(key, value);\n },\n remove: (key) => {\n values.delete(key);\n },\n };\n}\n","import { resolveAnnouncement, resolveTour } from '../define';\nimport type { AnnouncementDefinition, TextLocale, TourDefinition } from '../define';\nimport { DEFAULT_POLICY, pickInterruption } from '../policy';\nimport type { InterruptionBudget, TourRecord } from '../policy';\nimport type {\n Announcement,\n Inbox,\n InterruptionPolicy,\n KeyValueStore,\n Platform,\n ProgressInput,\n Tour,\n SaparSource,\n} from '../types';\n\nexport interface MemorySourceOptions {\n tours: readonly TourDefinition[];\n announcements?: readonly AnnouncementDefinition[];\n storage: KeyValueStore;\n locale?: string | (() => string);\n fallbackLocale?: string;\n /**\n * Applied to every resolved string at read time, after locale resolution. Lets definitions hold message keys\n * resolved by a stable function, so the source identity survives a locale switch.\n */\n translate?: (text: string) => string;\n policy?: Partial<InterruptionPolicy>;\n registeredAt?: Date | string | null;\n now?: () => number;\n namespace?: string;\n}\n\ninterface StoredCell<T> {\n read(): Promise<T>;\n write(value: T): Promise<void>;\n}\n\nconst isRecordMap = (value: unknown): value is Record<string, TourRecord> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every((item) => typeof item === 'string');\n\nfunction parseJson(raw: string | null): unknown {\n try {\n return raw === null ? undefined : JSON.parse(raw);\n } catch {\n return undefined;\n }\n}\n\n// Corrupt or foreign data reads as empty and is overwritten by the next write.\nfunction storedCell<T>(\n store: KeyValueStore,\n key: string,\n isValid: (value: unknown) => value is T,\n empty: () => T,\n): StoredCell<T> {\n return {\n async read() {\n const value = parseJson(await store.get(key));\n return isValid(value) ? value : empty();\n },\n async write(value) {\n await store.set(key, JSON.stringify(value));\n },\n };\n}\n\nfunction translateTour(tour: Tour, translate: (text: string) => string): Tour {\n return {\n ...tour,\n title: translate(tour.title),\n steps: tour.steps.map((step) => ({ ...step, title: translate(step.title), body: translate(step.body) })),\n };\n}\n\nfunction translateAnnouncement(announcement: Announcement, translate: (text: string) => string): Announcement {\n const { cta } = announcement;\n return {\n ...announcement,\n title: translate(announcement.title),\n body: translate(announcement.body),\n ...(cta && { cta: { ...cta, label: translate(cta.label) } }),\n };\n}\n\nconst recordId = (key: string, revision: number): string => `${key}@${revision}`;\n\nfunction toEpochMs(value: Date | string | null | undefined): number | null {\n return value == null ? null : new Date(value).getTime();\n}\n\nfunction latestAutoStart(records: Record<string, TourRecord>): number | null {\n const starts = Object.values(records)\n .filter((record) => record.autoStarted)\n .map((record) => record.startedAt);\n return starts.length > 0 ? Math.max(...starts) : null;\n}\n\nexport function memorySource(options: MemorySourceOptions): SaparSource {\n const { storage, namespace = 'sapar', fallbackLocale = 'en', translate = (text: string) => text } = options;\n const policy = { ...DEFAULT_POLICY, ...options.policy };\n const clock = options.now ?? Date.now;\n const registeredAt = toEpochMs(options.registeredAt);\n const announcementDefinitions = options.announcements ?? [];\n\n const tourRecords = storedCell<Record<string, TourRecord>>(storage, `${namespace}.tour-records`, isRecordMap, () => ({}));\n const seenKeys = storedCell<string[]>(storage, `${namespace}.seen`, isStringArray, () => []);\n const dismissedKeys = storedCell<string[]>(storage, `${namespace}.dismissed`, isStringArray, () => []);\n\n // Every operation is a read-modify-write on an async store; running them one at a time prevents lost updates.\n let queue: Promise<unknown> = Promise.resolve();\n function serialized<T>(task: () => Promise<T>): Promise<T> {\n const result = queue.then(task);\n queue = result.catch(() => undefined);\n return result;\n }\n\n function textLocale(): TextLocale {\n const { locale = fallbackLocale } = options;\n return { locale: typeof locale === 'function' ? locale() : locale, fallbackLocale };\n }\n\n const readTour = (definition: TourDefinition): Tour => translateTour(resolveTour(definition, textLocale()), translate);\n\n function findTour(key: string): TourDefinition {\n const tour = options.tours.find((candidate) => candidate.key === key);\n if (!tour) throw new Error(`No tour with key \"${key}\".`);\n return tour;\n }\n\n const isKnownAnnouncement = (key: string): boolean =>\n announcementDefinitions.some((announcement) => announcement.key === key);\n\n function requireAnnouncement(key: string): void {\n if (!isKnownAnnouncement(key)) throw new Error(`No announcement with key \"${key}\".`);\n }\n\n async function publishedAnnouncements(now: number): Promise<Announcement[]> {\n const seen = new Set(await seenKeys.read());\n const locale = textLocale();\n return announcementDefinitions\n .filter((announcement) => Date.parse(announcement.publishedAt) <= now)\n .sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt) || (a.key < b.key ? -1 : 1))\n .map((announcement) =>\n translateAnnouncement(resolveAnnouncement(announcement, locale, !seen.has(announcement.key)), translate),\n );\n }\n\n async function addKeys(cell: StoredCell<string[]>, keys: readonly string[]): Promise<void> {\n const merged = new Set([...(await cell.read()), ...keys]);\n await cell.write([...merged]);\n }\n\n async function getInbox({ route, platform }: { route?: string; platform: Platform }): Promise<Inbox> {\n const now = clock();\n const records = await tourRecords.read();\n const items = await publishedAnnouncements(now);\n const budget: InterruptionBudget = { policy, now, registeredAt, lastAutoInterruptionAt: latestAutoStart(records) };\n\n const interruption = pickInterruption(budget, {\n route,\n // The controller sends an already-normalized route.\n locales: [],\n tours: options.tours.filter((tour) => tour.platforms?.includes(platform) ?? true).map(readTour),\n stateOf: (tour) => records[recordId(tour.key, tour.revision)],\n announcements: items,\n dismissedKeys: new Set(await dismissedKeys.read()),\n });\n return {\n unreadCount: items.filter((item) => item.unread && item.tier !== 'silent').length,\n items,\n interruption,\n };\n }\n\n async function recordProgress({ key, revision, status, stepIndex, autoStarted }: ProgressInput): Promise<void> {\n findTour(key);\n const records = await tourRecords.read();\n const id = recordId(key, revision);\n const existing = records[id];\n const now = clock();\n\n // Same rules as the backend's record_tour_progress: completion and step index never regress, auto-start is sticky.\n const finalStatus = existing?.status === 'completed' ? 'completed' : status;\n records[id] = {\n status: finalStatus,\n revision,\n lastStepIndex: Math.max(stepIndex, existing?.lastStepIndex ?? 0),\n startedAt: existing?.startedAt ?? now,\n dismissedAt: existing?.dismissedAt ?? (finalStatus === 'dismissed' ? now : null),\n autoStarted: autoStarted || (existing?.autoStarted ?? false),\n };\n await tourRecords.write(records);\n }\n\n async function markSeen(keys: string[]): Promise<void> {\n await addKeys(seenKeys, keys.filter(isKnownAnnouncement));\n }\n\n return {\n getInbox: (query) => serialized(() => getInbox(query)),\n getTour: (key) =>\n serialized(async () => {\n const tour = options.tours.find((candidate) => candidate.key === key);\n return tour ? readTour(tour) : null;\n }),\n recordProgress: (input) => serialized(() => recordProgress(input)),\n markSeen: (keys) => serialized(() => markSeen(keys)),\n dismiss: (key) =>\n serialized(async () => {\n requireAnnouncement(key);\n await addKeys(seenKeys, [key]);\n await addKeys(dismissedKeys, [key]);\n }),\n recordClick: (key) =>\n serialized(async () => {\n requireAnnouncement(key);\n await addKeys(seenKeys, [key]);\n }),\n listAnnouncements: () => serialized(() => publishedAnnouncements(clock())),\n };\n}\n","import type {\n Announcement,\n AnnouncementTier,\n Inbox,\n Interruption,\n Placement,\n ProgressInput,\n Tour,\n TourStatus,\n TourStep,\n} from './types';\n\nexport interface WireStep {\n step_key: string;\n order: number;\n /** Empty means a centered step. */\n anchor_key: string;\n /** Empty means the current route. */\n route: string;\n title: string;\n body: string;\n placement: Placement;\n spotlight_padding: number;\n is_required: boolean;\n}\n\nexport interface WireTour {\n key: string;\n revision: number;\n title: string;\n trigger: 'manual' | 'route';\n steps: WireStep[];\n}\n\nexport interface WireTourState {\n status: TourStatus;\n last_step_index: number;\n completed_at: string | null;\n dismissed_at: string | null;\n}\n\nexport interface WireTourDetail extends WireTour {\n state: WireTourState | null;\n}\n\nexport interface WireAnnouncement {\n key: string;\n tier: AnnouncementTier;\n title: string;\n body: string;\n image_url: string;\n cta_label: string;\n cta_url: string;\n published_at: string | null;\n tour_key: string | null;\n is_unread: boolean;\n}\n\nexport interface WireInterruption {\n kind: 'tour' | 'announcement';\n tour: WireTour | null;\n announcement: WireAnnouncement | null;\n}\n\nexport interface WireInbox {\n unread_count: number;\n items: WireAnnouncement[];\n interruption: WireInterruption | null;\n}\n\nexport interface WireProgress {\n status: TourStatus;\n step_index: number;\n auto_started: boolean;\n}\n\nfunction fromWireStep(step: WireStep): TourStep {\n return {\n key: step.step_key,\n ...(step.anchor_key && { anchor: step.anchor_key }),\n ...(step.route && { route: step.route }),\n title: step.title,\n body: step.body,\n placement: step.placement,\n padding: step.spotlight_padding,\n required: step.is_required,\n advanceOn: 'next',\n };\n}\n\n// The payload carries no route pattern, so a route-triggered tour maps to the match-everything pattern.\nexport function fromWireTour(tour: WireTour): Tour {\n return {\n key: tour.key,\n revision: tour.revision,\n title: tour.title,\n trigger: tour.trigger === 'route' ? { route: '*' } : 'manual',\n steps: [...tour.steps].sort((a, b) => a.order - b.order).map(fromWireStep),\n };\n}\n\nexport function fromWireAnnouncement(announcement: WireAnnouncement): Announcement {\n return {\n key: announcement.key,\n tier: announcement.tier,\n title: announcement.title,\n body: announcement.body,\n ...(announcement.image_url && { imageUrl: announcement.image_url }),\n ...(announcement.cta_url && { cta: { label: announcement.cta_label, url: announcement.cta_url } }),\n publishedAt: announcement.published_at,\n ...(announcement.tour_key && { tourKey: announcement.tour_key }),\n unread: announcement.is_unread,\n };\n}\n\nfunction fromWireInterruption({ kind, tour, announcement }: WireInterruption): Interruption | null {\n if (kind === 'tour' && tour) return { kind, tour: fromWireTour(tour) };\n if (kind === 'announcement' && announcement) return { kind, announcement: fromWireAnnouncement(announcement) };\n return null;\n}\n\nexport function fromWireInbox(inbox: WireInbox): Inbox {\n return {\n unreadCount: inbox.unread_count,\n items: inbox.items.map(fromWireAnnouncement),\n interruption: inbox.interruption && fromWireInterruption(inbox.interruption),\n };\n}\n\n// The backend takes no revision: it records progress against the tour's current one.\nexport function toWireProgress({ status, stepIndex, autoStarted }: ProgressInput): WireProgress {\n return { status, step_index: stepIndex, auto_started: autoStarted };\n}\n","import { fromWireAnnouncement, fromWireInbox, fromWireTour, toWireProgress } from '../wire';\nimport type { WireAnnouncement, WireInbox, WireTour } from '../wire';\nimport type { Inbox, ProgressInput, SaparSource } from '../types';\n\nexport interface HttpSourceOptions {\n baseUrl: string;\n fetch?: typeof fetch;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n credentials?: RequestCredentials;\n locale?: string | (() => string);\n}\n\nexport class SaparHttpError extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = 'SaparHttpError';\n }\n}\n\ntype Query = Record<string, string | undefined>;\n\nfunction resolveValue<T>(value: T | (() => T)): T {\n return typeof value === 'function' ? (value as () => T)() : value;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query: Query): string {\n const params = new URLSearchParams();\n Object.entries(query).forEach(([name, value]) => value !== undefined && params.set(name, value));\n const search = params.toString();\n return `${baseUrl.replace(/\\/+$/, '')}/${path}${search && `?${search}`}`;\n}\n\nexport function httpSource(options: HttpSourceOptions): SaparSource {\n async function send(method: 'GET' | 'POST', path: string, query: Query = {}, body?: unknown): Promise<Response> {\n const headers = new Headers(await resolveValue(options.headers ?? {}));\n headers.set('Accept', 'application/json');\n if (body !== undefined) headers.set('Content-Type', 'application/json');\n\n const response = await (options.fetch ?? fetch)(buildUrl(options.baseUrl, path, query), {\n method,\n headers,\n credentials: options.credentials,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n if (!response.ok) throw new SaparHttpError(response.status, `${method} ${path} failed with ${response.status}`);\n return response;\n }\n\n async function getJson<T>(path: string, query: Query = {}): Promise<T> {\n const lang = options.locale === undefined ? undefined : resolveValue(options.locale);\n const response = await send('GET', path, { lang, ...query });\n return (await response.json()) as T;\n }\n\n async function post(path: string, body?: unknown): Promise<void> {\n await send('POST', path, {}, body);\n }\n\n const tourPath = (key: string): string => `tours/${encodeURIComponent(key)}/`;\n\n return {\n async getInbox({ route, platform }): Promise<Inbox> {\n return fromWireInbox(await getJson<WireInbox>('inbox/', { route, platform }));\n },\n async getTour(key) {\n try {\n return fromWireTour(await getJson<WireTour>(tourPath(key)));\n } catch (error) {\n if (error instanceof SaparHttpError && error.status === 404) return null;\n throw error;\n }\n },\n recordProgress: (input: ProgressInput) => post(`${tourPath(input.key)}progress/`, toWireProgress(input)),\n markSeen: (keys) => post('seen/', { keys }),\n dismiss: (key) => post('dismiss/', { key }),\n recordClick: (key) => post(`announcements/${encodeURIComponent(key)}/click/`),\n async listAnnouncements() {\n return (await getJson<WireAnnouncement[]>('announcements/')).map(fromWireAnnouncement);\n },\n };\n}\n","import type { AnchorHandle, AnchorRect, Platform, PlatformAdapter } from './types';\n\nexport interface TestAnchorHandle extends AnchorHandle {\n press(): void;\n pressListenerCount(): number;\n}\n\nexport interface TestAdapterOptions {\n platform?: Platform;\n /** Anchors that exist from the start. */\n anchors?: readonly string[];\n /** Anchors that resolve only once `showAnchor` is called, or to null on timeout. */\n delayedAnchors?: readonly string[];\n route?: string;\n now?: number;\n reducedMotion?: boolean;\n compact?: boolean;\n}\n\nexport interface TestAdapter extends PlatformAdapter {\n anchors: Map<string, TestAnchorHandle>;\n navigations: string[];\n scrolls: Array<{ handle: AnchorHandle; reducedMotion: boolean }>;\n resolveRequests: Array<{ key: string; timeoutMs: number }>;\n sessionValues: Map<string, string>;\n showAnchor(key: string): TestAnchorHandle;\n hideAnchor(key: string): void;\n setRoute(route: string): void;\n advanceTime(ms: number): void;\n}\n\nconst RECT: AnchorRect = { x: 0, y: 0, width: 100, height: 40 };\n\nfunction createAnchorHandle(key: string): TestAnchorHandle {\n const listeners = new Set<() => void>();\n return {\n key,\n measure: async () => RECT,\n onPress(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n press: () => [...listeners].forEach((listener) => listener()),\n pressListenerCount: () => listeners.size,\n };\n}\n\nexport function createTestAdapter(options: TestAdapterOptions = {}): TestAdapter {\n const anchors = new Map<string, TestAnchorHandle>();\n const waiting = new Map<string, Array<(handle: TestAnchorHandle) => void>>();\n const delayed = new Set(options.delayedAnchors);\n const sessionValues = new Map<string, string>();\n let route = options.route ?? '/';\n let now = options.now ?? 0;\n\n function showAnchor(key: string): TestAnchorHandle {\n const handle = anchors.get(key) ?? createAnchorHandle(key);\n anchors.set(key, handle);\n waiting.get(key)?.forEach((resolve) => resolve(handle));\n waiting.delete(key);\n return handle;\n }\n\n function waitForAnchor(key: string, timeoutMs: number): Promise<TestAnchorHandle | null> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => resolve(null), timeoutMs);\n waiting.set(key, [\n ...(waiting.get(key) ?? []),\n (handle) => {\n clearTimeout(timer);\n resolve(handle);\n },\n ]);\n });\n }\n\n options.anchors?.forEach(showAnchor);\n\n return {\n platform: options.platform ?? 'web',\n anchors,\n navigations: [],\n scrolls: [],\n resolveRequests: [],\n sessionValues,\n showAnchor,\n hideAnchor: (key) => void anchors.delete(key),\n setRoute: (next) => {\n route = next;\n },\n advanceTime: (ms) => {\n now += ms;\n },\n async resolveAnchor(key, { timeoutMs }) {\n this.resolveRequests.push({ key, timeoutMs });\n const present = anchors.get(key);\n if (present) return present;\n return delayed.has(key) ? waitForAnchor(key, timeoutMs) : null;\n },\n async scrollIntoView(handle, { reducedMotion }) {\n this.scrolls.push({ handle, reducedMotion });\n },\n currentRoute: () => route,\n async navigate(destination) {\n this.navigations.push(destination);\n route = destination;\n },\n prefersReducedMotion: () => options.reducedMotion ?? false,\n isCompact: () => options.compact ?? false,\n session: {\n get: (key) => sessionValues.get(key) ?? null,\n set: (key, value) => void sessionValues.set(key, value),\n },\n now: () => now,\n };\n}\n","import type { AnchorRect } from './types';\n\nexport interface SpotlightPathInput {\n /** Null dims the whole window. */\n rect: AnchorRect | null;\n padding: number;\n radius: number;\n bounds: { width: number; height: number };\n}\n\nfunction roundedRectPath(x: number, y: number, width: number, height: number, requestedRadius: number): string {\n const r = Math.max(0, Math.min(requestedRadius, width / 2, height / 2));\n return [\n `M${x + r},${y}`,\n `H${x + width - r}`,\n `A${r},${r} 0 0 1 ${x + width},${y + r}`,\n `V${y + height - r}`,\n `A${r},${r} 0 0 1 ${x + width - r},${y + height}`,\n `H${x + r}`,\n `A${r},${r} 0 0 1 ${x},${y + height - r}`,\n `V${y + r}`,\n `A${r},${r} 0 0 1 ${x + r},${y}`,\n 'Z',\n ].join(' ');\n}\n\n/** The bounds with a rounded hole around `rect`; fill it with the even-odd rule so the hole stays click-through. */\nexport function spotlightPath({ rect, padding, radius, bounds }: SpotlightPathInput): string {\n const outer = `M0,0 H${bounds.width} V${bounds.height} H0 Z`;\n if (!rect) return outer;\n\n const cutout = roundedRectPath(rect.x - padding, rect.y - padding, rect.width + padding * 2, rect.height + padding * 2, radius);\n return `${outer} ${cutout}`;\n}\n","export type Presentation = 'auto' | 'anchored' | 'sheet';\n\nexport interface PresentationInput {\n presentation: Presentation;\n /** The adapter reports a window narrower than its compact breakpoint. */\n compact: boolean;\n /** Whether the measured card fits beside the anchor; `undefined` until the card has been measured. */\n fits: boolean | undefined;\n}\n\n/** `'auto'` is anchored unless the window is compact or the card is known not to fit beside the anchor. */\nexport function resolvePresentation({ presentation, compact, fits }: PresentationInput): Exclude<Presentation, 'auto'> {\n if (presentation !== 'auto') return presentation;\n return compact || fits === false ? 'sheet' : 'anchored';\n}\n"],"mappings":"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","names":["next"]}
@@ -0,0 +1,176 @@
1
+ import {
2
+ createSaparController
3
+ } from "./chunk-FM64UDKJ.js";
4
+
5
+ // src/react/SaparProvider.tsx
6
+ import { useEffect, useMemo, useRef } from "react";
7
+
8
+ // src/react/context.ts
9
+ import { createContext, useContext } from "react";
10
+ var SaparContext = createContext(null);
11
+ function useSaparContext() {
12
+ const value = useContext(SaparContext);
13
+ if (!value) throw new Error("Sapar hooks must be used inside <SaparProvider> (or <NextSaparProvider>).");
14
+ return value;
15
+ }
16
+ function useSaparController() {
17
+ return useSaparContext().controller;
18
+ }
19
+ function useSaparAdapter() {
20
+ return useSaparContext().adapter;
21
+ }
22
+
23
+ // src/react/SaparProvider.tsx
24
+ import { jsx } from "react/jsx-runtime";
25
+ function ControllerBoundary({
26
+ controller,
27
+ adapter,
28
+ route,
29
+ children
30
+ }) {
31
+ useEffect(() => controller.setRoute(route), [controller, route]);
32
+ const value = useMemo(() => ({ controller, adapter }), [controller, adapter]);
33
+ return /* @__PURE__ */ jsx(SaparContext.Provider, { value, children });
34
+ }
35
+ function useDestroyOnUnmount(controller) {
36
+ const live = useRef(null);
37
+ useEffect(() => {
38
+ live.current = controller;
39
+ return () => {
40
+ live.current = null;
41
+ queueMicrotask(() => {
42
+ if (live.current !== controller) controller.destroy();
43
+ });
44
+ };
45
+ }, [controller]);
46
+ }
47
+ function OwnedControllerProvider({ options, route, children }) {
48
+ const { source, adapter } = options;
49
+ const controller = useMemo(() => createSaparController(options), [source, adapter]);
50
+ useDestroyOnUnmount(controller);
51
+ return /* @__PURE__ */ jsx(ControllerBoundary, { controller, adapter, route, children });
52
+ }
53
+ function SaparProvider(props) {
54
+ if (props.controller) {
55
+ const { controller, adapter, route: route2, children: children2 } = props;
56
+ return /* @__PURE__ */ jsx(ControllerBoundary, { controller, adapter, route: route2, children: children2 });
57
+ }
58
+ const { children, route, ...options } = props;
59
+ return /* @__PURE__ */ jsx(OwnedControllerProvider, { options, route, children });
60
+ }
61
+
62
+ // src/react/hooks.ts
63
+ import { useMemo as useMemo2, useSyncExternalStore } from "react";
64
+ function useSaparSnapshot() {
65
+ const controller = useSaparController();
66
+ return useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
67
+ }
68
+ function useSapar() {
69
+ const controller = useSaparController();
70
+ const { inbox, inboxStatus, runner } = useSaparSnapshot();
71
+ return { startTour: controller.startTour, inbox, inboxStatus, isRunning: runner.phase === "running" };
72
+ }
73
+ function useTourStep() {
74
+ const controller = useSaparController();
75
+ const { runner } = useSaparSnapshot();
76
+ return useMemo2(() => {
77
+ const step = runner.tour?.steps[runner.index];
78
+ if (runner.phase !== "running" || !runner.tour || !step) return null;
79
+ const shown = runner.tour.steps.filter((candidate) => !runner.skipped.includes(candidate.key));
80
+ const position = shown.findIndex((candidate) => candidate.key === step.key) + 1;
81
+ return {
82
+ tour: runner.tour,
83
+ step,
84
+ position,
85
+ total: shown.length,
86
+ isFirst: position === 1,
87
+ isLast: position === shown.length,
88
+ handle: runner.handle,
89
+ next: controller.next,
90
+ previous: controller.previous,
91
+ skip: controller.skip
92
+ };
93
+ }, [controller, runner]);
94
+ }
95
+ function useWhatsNew() {
96
+ const controller = useSaparController();
97
+ const { inbox, inboxStatus } = useSaparSnapshot();
98
+ const actions = useMemo2(
99
+ () => ({
100
+ markAllSeen: async () => {
101
+ const unreadKeys = (controller.getSnapshot().inbox?.items ?? []).filter((item) => item.unread).map((item) => item.key);
102
+ if (unreadKeys.length > 0) await controller.markSeen(unreadKeys);
103
+ },
104
+ markSeen: controller.markSeen,
105
+ dismiss: controller.dismiss,
106
+ recordClick: controller.recordClick,
107
+ startTour: controller.startTour
108
+ }),
109
+ [controller]
110
+ );
111
+ return { items: inbox?.items ?? [], unreadCount: inbox?.unreadCount ?? 0, status: inboxStatus, ...actions };
112
+ }
113
+ function useAnnouncementInterruption() {
114
+ const controller = useSaparController();
115
+ const { inbox } = useSaparSnapshot();
116
+ const offered = inbox?.interruption;
117
+ const announcement = offered?.kind === "announcement" && offered.announcement.tier === "modal" ? offered.announcement : null;
118
+ return useMemo2(
119
+ () => ({
120
+ announcement,
121
+ dismiss: async () => {
122
+ if (announcement) await controller.dismiss(announcement.key);
123
+ },
124
+ acknowledge: async () => {
125
+ if (announcement) await controller.markSeen([announcement.key]);
126
+ }
127
+ }),
128
+ [controller, announcement]
129
+ );
130
+ }
131
+
132
+ // src/react/stepCount.ts
133
+ var defaultAnnounceStep = ({ position, total, title }) => `Step ${position} of ${total}: ${title}`;
134
+ var defaultProgress = ({ position, total }) => `${position} / ${total}`;
135
+
136
+ // src/react/useWhatsNewPanel.ts
137
+ import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
138
+ function useWhatsNewPanel(open) {
139
+ const controller = useSaparController();
140
+ const { items, status, recordClick, startTour } = useWhatsNew();
141
+ const [newKeys, setNewKeys] = useState(null);
142
+ const isOpeningHandled = useRef2(false);
143
+ useEffect2(() => {
144
+ if (!open) {
145
+ isOpeningHandled.current = false;
146
+ setNewKeys(null);
147
+ return;
148
+ }
149
+ if (isOpeningHandled.current) return;
150
+ isOpeningHandled.current = true;
151
+ const unreadKeys = (controller.getSnapshot().inbox?.items ?? []).filter((item) => item.unread).map((item) => item.key);
152
+ setNewKeys(new Set(unreadKeys));
153
+ if (unreadKeys.length > 0) void controller.markSeen(unreadKeys);
154
+ }, [open, controller]);
155
+ return {
156
+ items: items.map((item) => ({ ...item, isNew: newKeys ? newKeys.has(item.key) : item.unread })),
157
+ status,
158
+ recordClick,
159
+ startTour
160
+ };
161
+ }
162
+
163
+ export {
164
+ useSaparController,
165
+ useSaparAdapter,
166
+ SaparProvider,
167
+ useSaparSnapshot,
168
+ useSapar,
169
+ useTourStep,
170
+ useWhatsNew,
171
+ useAnnouncementInterruption,
172
+ defaultAnnounceStep,
173
+ defaultProgress,
174
+ useWhatsNewPanel
175
+ };
176
+ //# sourceMappingURL=chunk-VC6XLWM3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/SaparProvider.tsx","../src/react/context.ts","../src/react/hooks.ts","../src/react/stepCount.ts","../src/react/useWhatsNewPanel.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useRef, type ReactNode } from 'react';\nimport { createSaparController } from '../core';\nimport type { PlatformAdapter, SaparController, SaparControllerOptions } from '../core';\nimport { SaparContext } from './context';\n\ninterface CommonProps {\n children: ReactNode;\n /** Forwarded to the controller on change. Leave undefined to opt out of route triggers. */\n route?: string;\n}\n\nexport type SaparProviderProps = CommonProps &\n (\n | { controller: SaparController; adapter?: PlatformAdapter }\n | ({ controller?: undefined } & SaparControllerOptions)\n );\n\nfunction ControllerBoundary({\n controller,\n adapter,\n route,\n children,\n}: CommonProps & { controller: SaparController; adapter: PlatformAdapter | undefined }) {\n useEffect(() => controller.setRoute(route), [controller, route]);\n const value = useMemo(() => ({ controller, adapter }), [controller, adapter]);\n return <SaparContext.Provider value={value}>{children}</SaparContext.Provider>;\n}\n\n// Destroys are deferred a microtask so StrictMode's simulated unmount/remount keeps the controller alive.\nfunction useDestroyOnUnmount(controller: SaparController): void {\n const live = useRef<SaparController | null>(null);\n useEffect(() => {\n live.current = controller;\n return () => {\n live.current = null;\n queueMicrotask(() => {\n if (live.current !== controller) controller.destroy();\n });\n };\n }, [controller]);\n}\n\nfunction OwnedControllerProvider({ options, route, children }: CommonProps & { options: SaparControllerOptions }) {\n const { source, adapter } = options;\n // Only source and adapter identity recreate the controller; the other options are read at creation.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const controller = useMemo(() => createSaparController(options), [source, adapter]);\n useDestroyOnUnmount(controller);\n return (\n <ControllerBoundary controller={controller} adapter={adapter} route={route}>\n {children}\n </ControllerBoundary>\n );\n}\n\nexport function SaparProvider(props: SaparProviderProps) {\n if (props.controller) {\n const { controller, adapter, route, children } = props;\n return (\n <ControllerBoundary controller={controller} adapter={adapter} route={route}>\n {children}\n </ControllerBoundary>\n );\n }\n const { children, route, ...options } = props;\n return (\n <OwnedControllerProvider options={options} route={route}>\n {children}\n </OwnedControllerProvider>\n );\n}\n","'use client';\n\nimport { createContext, useContext } from 'react';\nimport type { PlatformAdapter, SaparController } from '../core';\n\nexport interface SaparContextValue {\n controller: SaparController;\n /** Absent when the host passed a ready-made controller without telling us its adapter. */\n adapter: PlatformAdapter | undefined;\n}\n\nexport const SaparContext = createContext<SaparContextValue | null>(null);\n\nfunction useSaparContext(): SaparContextValue {\n const value = useContext(SaparContext);\n if (!value) throw new Error('Sapar hooks must be used inside <SaparProvider> (or <NextSaparProvider>).');\n return value;\n}\n\nexport function useSaparController(): SaparController {\n return useSaparContext().controller;\n}\n\nexport function useSaparAdapter(): PlatformAdapter | undefined {\n return useSaparContext().adapter;\n}\n","'use client';\n\nimport { useMemo, useSyncExternalStore } from 'react';\nimport type {\n AnchorHandle,\n Announcement,\n Inbox,\n InboxStatus,\n Tour,\n TourStep as TourStepData,\n SaparSnapshot,\n} from '../core';\nimport { useSaparController } from './context';\n\nexport function useSaparSnapshot(): SaparSnapshot {\n const controller = useSaparController();\n return useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);\n}\n\nexport interface UseSaparResult {\n startTour: (tourOrKey: Tour | string) => Promise<void>;\n inbox: Inbox | null;\n inboxStatus: InboxStatus;\n isRunning: boolean;\n}\n\nexport function useSapar(): UseSaparResult {\n const controller = useSaparController();\n const { inbox, inboxStatus, runner } = useSaparSnapshot();\n return { startTour: controller.startTour, inbox, inboxStatus, isRunning: runner.phase === 'running' };\n}\n\nexport interface TourStepState {\n tour: Tour;\n step: TourStepData;\n /** One-based position among the steps that are expected to be shown. */\n position: number;\n total: number;\n isFirst: boolean;\n isLast: boolean;\n handle: AnchorHandle | null;\n next: () => Promise<void>;\n previous: () => Promise<void>;\n skip: () => void;\n}\n\nexport function useTourStep(): TourStepState | null {\n const controller = useSaparController();\n const { runner } = useSaparSnapshot();\n return useMemo(() => {\n const step = runner.tour?.steps[runner.index];\n if (runner.phase !== 'running' || !runner.tour || !step) return null;\n\n const shown = runner.tour.steps.filter((candidate) => !runner.skipped.includes(candidate.key));\n const position = shown.findIndex((candidate) => candidate.key === step.key) + 1;\n return {\n tour: runner.tour,\n step,\n position,\n total: shown.length,\n isFirst: position === 1,\n isLast: position === shown.length,\n handle: runner.handle,\n next: controller.next,\n previous: controller.previous,\n skip: controller.skip,\n };\n }, [controller, runner]);\n}\n\nexport interface UseWhatsNewResult {\n items: Announcement[];\n unreadCount: number;\n status: InboxStatus;\n /** Marks the items that are unread at the moment of the call. */\n markAllSeen: () => Promise<void>;\n markSeen: (keys: string[]) => Promise<void>;\n dismiss: (key: string) => Promise<void>;\n recordClick: (key: string) => void;\n startTour: (tourOrKey: Tour | string) => Promise<void>;\n}\n\nexport function useWhatsNew(): UseWhatsNewResult {\n const controller = useSaparController();\n const { inbox, inboxStatus } = useSaparSnapshot();\n const actions = useMemo(\n () => ({\n markAllSeen: async () => {\n const unreadKeys = (controller.getSnapshot().inbox?.items ?? [])\n .filter((item) => item.unread)\n .map((item) => item.key);\n if (unreadKeys.length > 0) await controller.markSeen(unreadKeys);\n },\n markSeen: controller.markSeen,\n dismiss: controller.dismiss,\n recordClick: controller.recordClick,\n startTour: controller.startTour,\n }),\n [controller],\n );\n return { items: inbox?.items ?? [], unreadCount: inbox?.unreadCount ?? 0, status: inboxStatus, ...actions };\n}\n\nexport interface AnnouncementInterruption {\n /** The modal-tier announcement the source wants shown now, if any. */\n announcement: Announcement | null;\n dismiss: () => Promise<void>;\n /** Marks the announcement seen without recording a dismissal. */\n acknowledge: () => Promise<void>;\n}\n\nexport function useAnnouncementInterruption(): AnnouncementInterruption {\n const controller = useSaparController();\n const { inbox } = useSaparSnapshot();\n const offered = inbox?.interruption;\n const announcement = offered?.kind === 'announcement' && offered.announcement.tier === 'modal' ? offered.announcement : null;\n\n return useMemo(\n () => ({\n announcement,\n dismiss: async () => {\n if (announcement) await controller.dismiss(announcement.key);\n },\n acknowledge: async () => {\n if (announcement) await controller.markSeen([announcement.key]);\n },\n }),\n [controller, announcement],\n );\n}\n","export interface StepCount {\n /** One-based. */\n position: number;\n total: number;\n}\n\nexport interface StepAnnouncement extends StepCount {\n title: string;\n}\n\nexport const defaultAnnounceStep = ({ position, total, title }: StepAnnouncement): string =>\n `Step ${position} of ${total}: ${title}`;\n\nexport const defaultProgress = ({ position, total }: StepCount): string => `${position} / ${total}`;\n","'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { Announcement, InboxStatus, Tour } from '../core';\nimport { useSaparController } from './context';\nimport { useWhatsNew } from './hooks';\n\nexport interface WhatsNewPanelItem extends Announcement {\n /** Unread when the panel was opened; stays true until it closes. */\n isNew: boolean;\n}\n\nexport interface UseWhatsNewPanelResult {\n items: WhatsNewPanelItem[];\n status: InboxStatus;\n recordClick: (key: string) => void;\n startTour: (tourOrKey: Tour | string) => Promise<void>;\n}\n\n/** Marks what was unread as seen once per opening, while `isNew` keeps pointing at it until the panel closes. */\nexport function useWhatsNewPanel(open: boolean): UseWhatsNewPanelResult {\n const controller = useSaparController();\n const { items, status, recordClick, startTour } = useWhatsNew();\n const [newKeys, setNewKeys] = useState<ReadonlySet<string> | null>(null);\n const isOpeningHandled = useRef(false);\n\n useEffect(() => {\n if (!open) {\n isOpeningHandled.current = false;\n setNewKeys(null);\n return;\n }\n if (isOpeningHandled.current) return;\n isOpeningHandled.current = true;\n\n const unreadKeys = (controller.getSnapshot().inbox?.items ?? []).filter((item) => item.unread).map((item) => item.key);\n setNewKeys(new Set(unreadKeys));\n if (unreadKeys.length > 0) void controller.markSeen(unreadKeys);\n }, [open, controller]);\n\n return {\n items: items.map((item) => ({ ...item, isNew: newKeys ? newKeys.has(item.key) : item.unread })),\n status,\n recordClick,\n startTour,\n };\n}\n"],"mappings":"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","names":["route","children","useMemo","useMemo","useEffect","useRef","useRef","useEffect"]}