# tasks — react-ui-registry-deployment ## 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. Registry build script (in `@suaveplan/react-ui`) - [ ] 1.1 Create `packages/react/react-ui/scripts/build-registry.ts`. Walks the package's component source tree and emits one `registry-item` JSON per component plus a top-level `registry/r/index.json` listing every entry. - [ ] 1.2 Match the shadcn `registry-item` v1 schema exactly: `$schema`, `name`, `type`, `dependencies`, `registryDependencies`, `files[]`, `tailwind`, `cssVars`. Validate the emitted JSON against a Zod schema before writing — the build fails on shape drift. - [ ] 1.3 Emit deterministic output: stable key order, LF line endings, trailing newline. The CI snapshot diff in the package's existing test suite will catch accidental drift. - [ ] 1.4 Co-locate `build-registry.md` per the genesis docs rule (≥200 words, Purpose / Features / Basic Example / Advanced Example / API Reference / Implementation Notes). - [ ] 1.5 Add a smoke test `packages/react/react-ui/scripts/build-registry.test.ts` that invokes the script in a temp dir, parses every emitted JSON, and asserts schema conformance. ## 2. Caddy + Docker scaffolding - [ ] 2.1 Write `ops/registry/Dockerfile` — `FROM caddy:2-alpine`, `COPY Caddyfile /etc/caddy/Caddyfile`, healthcheck on `/health`. - [ ] 2.2 Write `ops/registry/Caddyfile` — `ui.suaveplan.com` site block with: - `root * /srv/registry` - `handle_path /r/*` → `file_server` with `Access-Control-Allow-Origin: *` - `handle_path /storybook/*` → `file_server` with restrictive CSP - `handle /health` → `respond 200 "ok"` - `tls` block pinning `protocols tls1.2 tls1.3` - `header` block with HSTS, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy` - `encode gzip zstd` - explicit deny on `/.well-known/*` outside ACME - [ ] 2.3 Write `ops/registry/docker-compose.yml` — one service `registry`, port `80:80` and `443:443`, named volumes `caddy_data` and `caddy_config`, bind-mount `/srv/registry:/srv/registry:ro`, `restart: unless-stopped`. - [ ] 2.4 Write `ops/registry/.env.example` — placeholders `OVH_SSH_HOST=__OVH_HOST__`, `OVH_SSH_USER=deploy-registry`, `REGISTRY_DEPLOY_PATH=/srv/registry/r/`, `STORYBOOK_DEPLOY_PATH=/srv/registry/storybook/`. No real values. ## 3. Deployment runbook - [ ] 3.1 Write `ops/registry/README.md` covering: - Architecture overview (one Caddy container, two paths) - First-time setup (clone, env, `docker compose up -d`, cert issuance check) - Creating the `deploy-registry` user with scoped sudoers rule - Installing the CI public key on OVH - The on-deploy flow (path-filtered GitHub Action, rsync, no-reload-needed-for-static) - Manual ad-hoc admin via `ssh root@ovh` (out-of-band; CI intentionally does not use root) - Rollback via `bash ops/registry/rollback.sh` (one-shot atomic swap from `/srv/registry/.previous//`) - Cert renewal (Caddy handles automatically; how to verify and how to force-renew if needed) - GitHub Actions secret rotation steps - [ ] 3.2 Write `ops/registry/rollback.sh` referenced from the runbook. Idempotent, exits non-zero with a clear message if no snapshot exists. ## 4. CI workflow - [ ] 4.1 Write `.github/workflows/registry-deploy.yml` — triggers on push to `genesis` with path filter `packages/react/react-ui/**` plus `ops/registry/**`. Job steps: - `actions/checkout@v4` - Setup Bun via `oven-sh/setup-bun@v2` - `bun install --frozen-lockfile` - `bunx turbo build --filter=@suaveplan/react-ui` - `bun packages/react/react-ui/scripts/build-registry.ts` - `cd packages/react/react-ui && bun run build-storybook` - `webfactory/ssh-agent@v0.9.0` with the `OVH_SSH_PRIVATE_KEY` secret - `rsync -azh --delete --backup --backup-dir=/srv/registry/.previous/$(date -u +%Y%m%dT%H%M%SZ)/r/ packages/react/react-ui/registry/r/ "$OVH_SSH_USER@$OVH_SSH_HOST:/srv/registry/r/"` - same for storybook with its own `--backup-dir` - post-deploy `curl -fsSL https://ui.suaveplan.com/health` smoke test - [ ] 4.2 Add a `concurrency:` block keyed on the workflow + ref so back-to-back pushes serialise, never race. - [ ] 4.3 Document the three required GitHub Actions secrets in the runbook (`OVH_SSH_PRIVATE_KEY`, `OVH_SSH_HOST`, `OVH_SSH_USER`). NEVER commit values. ## 5. Manual user steps (call out clearly) The user MUST perform these one-time manual steps; they cannot be scripted in this change because they involve secrets and OVH host-side configuration. - [ ] 5.1 **USER:** Generate a deploy SSH keypair locally (`ssh-keygen -t ed25519 -C "registry-deploy" -f ~/.ssh/registry_deploy`). - [ ] 5.2 **USER:** Copy the public key to OVH under the `deploy-registry` user's `~/.ssh/authorized_keys` (after creating the user per the runbook). - [ ] 5.3 **USER:** Add the private key as the `OVH_SSH_PRIVATE_KEY` secret in the GitHub repo settings. - [ ] 5.4 **USER:** Add `OVH_SSH_HOST` and `OVH_SSH_USER` as GitHub Actions secrets (host = the OVH hostname, user = `deploy-registry`). - [ ] 5.5 **USER:** Confirm `ui.suaveplan.com` A record points to the OVH IP (already done per change context, but re-verify with `dig +short ui.suaveplan.com` before first deploy). ## 6. Verification (Web App & Page Verification gate) Per CLAUDE.md "Web App & Page Verification" — required even though this is mostly static content. - [ ] 6.1 Deploy from a feature branch first (NOT `genesis`) against a staging path or a temporary subdomain to validate the pipeline before pointing it at production. - [ ] 6.2 Run a Playwright sweep: navigate to `/`, `/r/index.json`, `/storybook/`, plus a sampled subset of per-component manifests. Assert HTTPS, valid cert, expected DOM content, zero `console.error`, zero failed subresource requests. - [ ] 6.3 End-to-end shadcn-add test: spin up a clean Vite + React project in a temp dir, point its `components.json` at the public registry, run `npx shadcn@latest add @suaveplan/button`, assert `src/components/ui/button.tsx` lands with the expected exports. This is the contract test. - [ ] 6.4 Capture browser console + network during the Playwright sweep; any unexpected warning or 4xx/5xx fails the gate. - [ ] 6.5 Run the rollback script once on the OVH host and verify the previous registry contents serve. Roll forward again before closing the task. - [ ] 6.6 24 h after first production deploy, query the Caddy access log and confirm 5xx count is zero. ## 7. Documentation + change archive - [ ] 7.1 Refresh root docs per AGENTS §13b: `MANIFEST.md`, `MISSING.yml`, `STATUS.md`, `SUMMARY.md`. The `STATUS.md` row for ops infrastructure should reflect that the registry deploy exists. - [ ] 7.2 Run `bunx markdownlint-cli2 --fix` on every touched Markdown file; resolve any unfixable violations by hand. - [ ] 7.3 Fold `specs/registry-deployment/spec.md` into the canonical `openspec/specs/registry-deployment/spec.md` (a new capability spec — first ops-tier capability on genesis). - [ ] 7.4 Move `openspec/changes/react-ui-registry-deployment/` to `openspec/archive/-react-ui-registry-deployment/`.