chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
# App Platform — Phase 1: Serve Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Serve static sites from the account repo (blobs + a `dev.pdsjs.site.deploy` manifest record) on the deployment's apex and one-label subdomains, via a new `@pdsjs/sites` package and one hostname-dispatch seam in core, with a deploy CLI and an account-UI Sites page.
**Architecture:** New workspace package `packages/sites`, workspace-only dependency on `@pdsjs/core`. Core gains `config.siteHandler` / `config.siteBrowser` and one hostname check at the top of `fetch()`; platform packages gate on `PDS_SITE_DOMAIN` and compose the handler from ports. Site content never renders on the PDS origin.
**Tech Stack:** plain ESM JS + JSDoc types, vitest, biome (single quotes, 2-space indent), one commit per task.
## Global Constraints
- Spec: `docs/superpowers/specs/2026-08-06-app-platform-serve.md`.
- `@pdsjs/sites` has **zero external dependencies**; workspace-only dependency on `@pdsjs/core`. `src/cli.js` is the only file allowed to import `node:*` modules, and nothing on the serve path imports it.
- `@pdsjs/core` must never import `@pdsjs/sites`. Dependency direction is one-way.
- **No dependency on `@pdsjs/spaces`** in any direction; everything works with `PDS_ENABLE_SPACES` off.
- No `any` in library type annotations; `/** @type {any} */` casts are allowed only in test mocks.
- Tests at `packages/sites/test/*.test.js` (vitest already includes them). CI runs biome + `tsc --noEmit` + unit tests only — unit coverage gates everything.
- The flag-off path is load-bearing: with no `siteHandler`, foreign-Host requests behave byte-identically to today. Explicit test.
- Branch: `app-platform-serve`.
---
### Task 1: Spec + scaffolding + hostname resolution
Create `packages/sites/{package.json, src/index.js, src/hostname.js, src/lexicons.js}` and `test/hostname.test.js` (apex match, valid/invalid labels, case folding, 63-char bounds, multi-label, unrelated domains → null).
### Task 2: Manifest loading
`src/manifest.js`: `getRecord` → `cborDecode` → one-hop `base` merge (refuse deeper) → `Map<path, entry>`; module-level LRU (≤32) keyed by record CID. Tests with an in-memory actorStorage mock built on `cborEncodeDagCbor`.
### Task 3: Serve handler
`src/serve.js` + `src/handler.js` (`createSiteHandler(ctx)`), per the spec's serving semantics §3. Tests: null on unrelated host; unknown-site 404; index/trailing-slash/301; traversal + bad-encoding 400s; SPA fallback 200; ETag/304 and HEAD asserted to make zero blob reads (mock call counts); MIME precedence; cacheControl override; 405s incl. OPTIONS; `/.well-known/atproto-did`.
### Task 4: Core seam
`packages/core/src/pds.js`: `siteHandler`/`siteBrowser` constructor options, `sitesEnabled`, hostname check at top of `fetch()` (before the OPTIONS short-circuit, outside `withDpopNonce`, no `addCorsHeaders`, before the account-app intercept). `packages/core/src/ports.js`: `SiteBrowserPort`. Seam tests via `pds.fetch` with foreign Host headers per spec §10; audit that no fall-through path derives OAuth issuer URLs from `url.host`.
### Task 5: Site browser + account API
`src/browser.js`; core `handleApiSites` / `handleApiSiteDelete` via the `accountApi` wrapper, route-table lines, `features.sites`. Tests in the account-page style + a readOnly-mode test.
### Task 6: Cloudflare wiring
Env typedef (`PDS_SITE_DOMAIN`, `PDS_SITE_APEX_NAME`), DO-constructor gate next to spaces, config passthrough; `examples/cloudflare/wrangler.toml` route/DNS comments; `.dev.vars` note. Tests: flag-off/flag-on via the d1-shim harness.
### Task 7: Node/Deno wiring
Dynamic import behind `PDS_SITE_DOMAIN`, optional peer dep, README notes (reverse proxy, wildcard certs). Tests: flag-off/flag-on.
### Task 8: Deploy CLI
`src/deploy.js` pure helpers (walk/diff/record-build) + `src/cli.js` (`pdsjs-site deploy`). Tests: CID-stable diff/skip, oversize abort, record-size abort, MIME assignment.
### Task 9: Account-UI Sites page
`packages/account-ui/src/pages/sites.jsx`, `app.jsx` wiring (sectionsFor/PAGES/dispatch), `queryKeys.sites`; rebuild assets.
### Task 10: README
Root README package table, architecture diagram arrow (platforms → sites → core), env-var docs.
### Later phases (not this plan)
Grant (install gate + consent skip), store/install flow + Store UI, Query, React, Schedule — sketched in the spec §9.