# OpenSpec Change: enterprise-browser-react-tour - **Change ID**: enterprise-browser-react-tour - **Package**: `@suaveplan/react-tour` - **Category**: browser - **Target path**: `browser/react/react-tour` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/react-tour.md` --- ## Summary Deliver `@suaveplan/react-tour` on the genesis branch at enterprise quality. The package provides a headless `useTour` hook for guided product tours with step navigation, spotlight targeting, scroll-into-view, and completion persistence. Genesis adds Zod-validated options, live `targetRect` tracking via `ResizeObserver`/`IntersectionObserver`, a `PersistenceAdapter` interface for injectable storage, `@suaveplan/events` lifecycle events, structured logging via `@suaveplan/logger`, SSR safety guards, Storybook stories, and co-located `.md` docs. --- ## Current-state audit (genesis) Fresh build — does not exist on genesis yet. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `TourConfigError`, `TOUR_STEP_NOT_FOUND` | | `@suaveplan/events` (`workspace:^`) | `tour:start`, `tour:step-change`, `tour:complete`, `tour:skip`, `tour:error` | | `@suaveplan/logger` (`workspace:^`) | Structured logging for storage failures | | `@suaveplan/schema-registry` (`workspace:^`) | Register `TourStep` schema | | `zod` (`catalog:forms`) | `TourConfig` + `TourStep` Zod schemas | | `@opentelemetry/api` (`catalog:observability`) | Step-view / completion metrics | | `tslib` (`catalog:build`) | Runtime helpers | Dev: `vitest`, `@vitest/coverage-istanbul`, `happy-dom`, `@testing-library/react`, `@suaveplan/testing`, `@suaveplan/testing-dom`. --- ## Preserved public API surface ```ts function useTour(config: TourConfig): [TourState, TourActions] // Utils function calculateSpotlightInset(rect: DOMRect, padding?: number): SpotlightInset function calculateTooltipPosition(rect: DOMRect, placement: TooltipPlacement): TooltipPosition function getTargetRect(selector: string): DOMRect | null function scrollTargetIntoView(selector: string): Promise // Persistence (new injectable interface) interface PersistenceAdapter { isCompleted(key: string): boolean; markCompleted(key: string): void; clear(key: string): void } // Types interface TourConfig { steps: TourStep[]; storageKey?: string; persistence?: PersistenceAdapter; onComplete?(): void; onSkip?(): void } interface TourStep { id: string; target?: string; title: string; content: string; placement?: TooltipPlacement; shouldSkip?(ctx: TourState): boolean } interface TourState { isActive: boolean; currentStep: number; totalSteps: number; isFirst: boolean; isLast: boolean; progress: number; targetRect: DOMRect | null } interface TourActions { next(): void; prev(): void; goTo(n: number): void; skip(): void; reset(): void } ``` Subpath exports: `"."`, `"./core/tour/tour"`, `"./utils/positioning"`, `"./persistence"`. --- ## New additions for genesis ### 1. Zod-validated options `TourConfigSchema` and `TourStepSchema` validated at hook entry; throws `TourConfigError` (extends `@suaveplan/error`) on invalid input. Registered in `@suaveplan/schema-registry`. ### 2. Live `targetRect` tracking Replace the 100 ms `setTimeout` with a composite observer (`ResizeObserver` + `IntersectionObserver` + scroll listener) in `src/utils/observe-target.ts` so `targetRect` stays accurate during layout shifts. ### 3. `PersistenceAdapter` interface `LocalStoragePersistence` (default), `SessionStoragePersistence`, `MemoryPersistence` (for tests), injectable via `TourConfig.persistence`. SSR-safe: `typeof window` guards everywhere. ### 4. Event lifecycle via `@suaveplan/events` Emits `tour:start`, `tour:step-change`, `tour:complete`, `tour:skip`, `tour:error` so analytics and observability consumers can subscribe. ### 5. Structured logging `@suaveplan/logger` replaces the three bare `catch {}` blocks in the original for storage and scroll errors. ### 6. Storybook stories `useTour.stories.tsx` — basic tour, spotlight overlay, scroll-into-view, completion persistence, accessibility-compliant tooltip overlay. ### 7. Error codes `TOUR_CONFIG_INVALID`, `TOUR_STEP_NOT_FOUND`. ### 8. OTel metrics Counter `tour.step_view_total`, counter `tour.complete_total`, counter `tour.skip_total`, histogram `tour.step_duration_ms`. --- ## File layout ```text browser/react/react-tour/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts core/ tour/ tour.ts (useTour hook, ≤250 lines) tour-state.ts (state machine helpers) tour-effects.ts (observer wiring + events) tour-actions.ts (next/prev/goTo/skip/reset) tour.test.ts tour.md tour.types.ts index.ts utils/ positioning/ positioning.ts (calculateSpotlightInset, calculateTooltipPosition, getTargetRect, scrollTargetIntoView) positioning.test.ts positioning.md positioning.types.ts index.ts observe-target/ observe-target.ts (ResizeObserver + IntersectionObserver + scroll) observe-target.test.ts observe-target.md index.ts persistence/ persistence/ persistence.ts (PersistenceAdapter interface + implementations) persistence.test.ts persistence.md index.ts schema/ tour-schema/ tour-schema.ts (TourConfigSchema, TourStepSchema) tour-schema.md index.ts stories/ use-tour.stories.tsx (basic, spotlight, scroll, persistence, a11y) types/ common.ts common.md constants/ index.ts tests/ integration.test.ts e2e/ react-tour.spec.ts (targetRect tracks resize/scroll, completion persistence survives reload) ``` --- ## Impact **Downstream**: any Suaveplan browser app using guided tours. **Breaking changes**: None — all additions are backward-compatible; `PersistenceAdapter` is opt-in. **Prerequisites**: `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/logger` on Verdaccio.