Files
2026-05-27 23:29:53 +01:00

4.2 KiB

Tasks: enterprise-react-loading

Change ID: enterprise-react-loading Package: @suaveplan/react-loading Target path: react/ui/react-loading Prerequisite packages on Verdaccio: @suaveplan/events, @suaveplan/error, @suaveplan/react-translate


0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create directory tree per proposal layout.
  • Write package.json at 0.1.0 with "sideEffects": false, exports map.
  • Dependencies: @suaveplan/events workspace:^, @suaveplan/error workspace:^, @suaveplan/react-translate workspace:^, zod catalog:forms, tslib catalog:build.
  • Write tsconfig.json, vitest.config.ts, scripts.

2. Types

  • Define LoadingTask: { id: string; label: string; weight: number; status: 'pending' | 'loading' | 'complete' | 'error' }.
  • Define LoadingState: { tasks: LoadingTask[]; progress: number; isComplete: boolean; currentTip: string | null }.
  • Define LoadingConfig: { tips?: string[]; tipIntervalMs?: number; minDisplayMs?: number }.

3. Core — LoadingTracker

  • Extend EventEmitter from @suaveplan/events.
  • Methods: addTask(task), updateTask(id, partial), completeTask(id), errorTask(id, error?), pause(), resume(), restart(), dispose().
  • progress getter — weighted aggregate: sum(complete.weight) / sum(all.weight).
  • Tip rotation via globalThis.setInterval with tipIntervalMs; dispose() clears it.
  • Emits 'change' event on any state mutation; 'tipChange' on tip rotation.
  • Write loading-tracker.test.ts (≤500 lines): add/complete/error tasks, weighted progress, pause/resume/restart, dispose clears timer, SSR (no window). { timeout: 10000 }.
  • Write loading-tracker.md.

4. Hook — useLoading

  • Binds LoadingTracker via useSyncExternalStore — subscribes to 'change' event, snapshot returns current LoadingState.
  • Write use-loading.test.tsx: initial state, task completion updates progress, subscribe fires re-render, unsubscribe on unmount. { timeout: 10000 }.
  • Write use-loading.stories.tsx: playground (add/complete tasks in story), default with pre-configured tracker.
  • Write use-loading.md.

5. Components

ProgressBar

  • Animated progress fill (CSS transition); prefers-reduced-motion disables animation.
  • Write test, stories (determinate, indeterminate, animated, reduced-motion), md.

TipCarousel

  • Displays currentTip from LoadingState; fade transition between tips.
  • prefers-reduced-motion disables fade.
  • Write test, stories, md.

TaskList

  • Lists tasks with status icons (pending/loading spinner/complete/error).
  • Write test, stories (all-statuses, empty, loading), md.

LoadingScreen

  • Full-viewport overlay: accepts tracker prop; renders ProgressBar, TipCarousel, TaskList (optional per prop flags).
  • Auto-hides when isComplete + optional hideDelayMs.
  • Write test, stories (default, with-tasks, with-tips, playground), md.

6. Integration

  • tests/integration.test.tsx — create LoadingTracker, add 3 weighted tasks, render LoadingScreen, complete tasks one by one, verify ProgressBar value and eventual hide.

7. Verify and publish

  • bunx turbo lint typecheck test --filter=@suaveplan/react-loading — green.
  • bunx turbo test:coverage --filter=@suaveplan/react-loading — 100%.
  • bun scripts/publish.ts --package @suaveplan/react-loading.
  • Archive change folder.