Files
suaveplan/openspec/changes/enterprise-browser-react-tour/proposal.md
2026-05-27 23:29:53 +01:00

6.1 KiB

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

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<void>

// 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

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.