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

3.8 KiB

Tasks: enterprise-react-image

Change ID: enterprise-react-image Package: @suaveplan/react-image Target path: react/media/react-image Prerequisite packages on Verdaccio: @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/error workspace:^, @suaveplan/react-translate workspace:^, zod catalog:forms, @opentelemetry/api catalog:observability, tslib catalog:build.
  • Write tsconfig.json, vitest.config.ts, scripts.

2. Error codes and types

  • Define ImageError extending SuaveplanError; codes: IMAGE_LOAD_FAILED, IMAGE_DECODE_FAILED, IMAGE_CROP_INVALID.
  • Define all component prop types in src/types/.

3. Utils

  • src/utils/blurhash/ — decodeBlurhash(hash, width, height): Uint8ClampedArray, blurhashToDataURL(hash, width, height): string. Write test + md.
  • src/utils/filter-presets/ — getFilterCSS(preset: FilterPreset): string (grayscale, sepia, blur, brightness, contrast, invert). Write test + md.

4. Hooks

For each hook (useImageLoader, useImagePreloader, useImageDimensions, useImageColor, useLightbox):

  • Implement hook. useImageLoader emits OTel image.load span. Throws ImageError on failure.
  • Write test (≤500 lines) + stories (playground) + md. { timeout: 10000 }.

5. Components

For each component:

  • Implement with i18n via useTranslation, forward ref, a11y attributes.
  • Image — lazy loading via IntersectionObserver, blurhash placeholder, skeleton fallback, srcSet.
  • Avatar/AvatarGroup — initials fallback, status indicator (online/offline/busy/away), group overlap.
  • ImageComparison — draggable split slider; keyboard adjustable.
  • ImageCropper — drag-handle crop selection; validates bounds (throws ImageError(IMAGE_CROP_INVALID) on invalid).
  • ImageFilter — CSS filter presets via getFilterCSS; preview toggle.
  • ImageGallery — masonry layout via CSS columns or absolute positioning.
  • ImageMagnifier — hover-activated lens at configurable zoom level.
  • ImageOverlay — gradient/reveal overlay wrapper.
  • ImagePlaceholder — SVG with aria-label.
  • Lightbox — portal-rendered; keyboard nav (Left/Right/Esc); thumbnails; close on backdrop.
  • ZoomImage — pinch-to-zoom (touch) + wheel-zoom; pan when zoomed.
  • Write test + stories (default, loading, error, interactive, playground) + md for each.

6. E2E

  • e2e/image.spec.ts — Playwright:
    • Image with lazy-load: scroll into view → img src set.
    • ImageCropper drag: drag handle, verify crop rect changes.
    • Lightbox: click image → lightbox opens; press Right → next image; press Esc → closes.

7. Verify and publish

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