--- id: site title: A stranger can read what this project is, without cloning it status: open crates: [didbot-site-anim, didbot-brand] dependsOn: [] exitCriterion: > `scripts/publish-site.sh` puts a build behind did.bot that a stranger can navigate end to end: marketing pages, every file in docs/, and the crate API reference, with no dead link between them. --- # site Everything a stranger can currently read about this project lives in a git checkout: the README, `docs/`, and whatever `cargo doc` renders from them. None of it is served anywhere. `site/` is a static build — Astro, built from scratch under that directory — that turns all three into one tree: marketing and the boring launch-required pages (privacy, terms, security, status, 404), `docs/*.md` rendered as prose with its links rewritten to resolve on the site, and `cargo doc --workspace --no-deps --document-private-items`'s own output mounted at `/api/`. `scripts/build-site.sh` builds all three together; `scripts/publish-site.sh` adds the S3 sync and CloudFront invalidation; `--dry-run` plans without publishing. The marketing pages other than the landing page have no real copy yet. [CLAUDE.md](../CLAUDE.md)'s Copywriting section says why: brand voice is a human's call, not something an agent decides while shipping infrastructure. Those pages' copy is collected in `site/src/data/*.ts`, one file per page or section, where a human writes it in place; every slot in it is still filler today, each wrapped in one marker. The landing page has already had that pass: its copy is the owner's own, in `site/src/data/landing-beats.ts`. `site/README.md` documents how a human finds what is still filler. ## Where it can serve from, and why that is not decided here [agent-sites](agent-sites.md) has an open question this epic's layout leans on without answering: an agent's own page and this server's own browser sessions cannot safely share a registrable domain, because a cookie set by one is visible to the other. did.bot is the answer to half of that — it is the *marketing* origin, a static build with no session of its own and nothing in it an agent wrote — which is why it is safe to put it on the apex domain regardless of how that question resolves. What is not decided here is whether an operator's dashboard ever shares an origin with this site, or whether agent-authored pages end up on a sibling of it; that is [agent-sites](agent-sites.md)'s call, not this epic's. Marketing prose is the plain, load-bearing layer — hospitality, in the project owner's words: the docs, `/api/`, get-started and the launch-required pages read as an ordinary site, quickly and legibly. Everything else on the atmosphere pages (home, features, architecture, about, contact) carries the site's own strangeness instead of announcing it: a context meter that fills with scroll depth and folds passed sections rather than a progress bar addressed to the visitor, a margin ticker of this repository's own real `git log` history rather than invented chatter, and a hover-visible content hash and commit on every docs page — real values computed at build time, never fabricated. Three testimonials from Opus, Fable and Haiku sit in `site/src/data/testimonials.ts` as provisional real copy — genuine quotations, each trimmed for the page with the untrimmed original reachable through a native disclosure, marked `provisional` so a future copy pass can find and replace them without the placeholder-coverage check treating "provisional" as license to invent one instead. The hero animation stays a cellular automaton (Rule 110) resolving into a force-directed graph, now in Rust — `crates/didbot-site-anim`, compiled to wasm by `scripts/build-wasm.sh` — so the simulation itself is covered by ordinary `cargo test` rather than only by eyeballing a canvas. A self-hosted `/.well-known/did.json` for did.bot itself was considered and declined: a real one needs a live signing key and a provisioned account behind it, which a static S3/CloudFront origin has neither of, and a did:web document with no working key material would be exactly the kind of fabricated artifact this epic otherwise refuses to ship. ## What ships - [ ] **A real bucket and distribution.** `infra/site/main.tf` creates the bucket `scripts/publish-site.sh` uploads to (`aws_s3_bucket.site`) and the CloudFront distribution in front of it (`aws_cloudfront_distribution.site`). - [ ] **Brand copy.** Every slot in `site/src/data/*.ts` is still filler. Somebody who is not this epic decides what the site actually says. - [ ] **The three testimonials, eventually replaced again.** The current quotations from Opus, Fable and Haiku are real but provisional; the project owner expects to refill these slots later, and the mechanism that makes that safe (not a licence to invent a replacement) is `site/README.md`'s job to keep documented, not this list's. ## Done - [x] **Astro under `site/`, from scratch**, pinned in a committed lockfile, with no Node tooling anywhere else in the repository. - [x] **The marketing and launch-required pages**, every sentence on them a searchable placeholder. - [x] **`docs/*.md` as a content collection**, copied and link-rewritten by `site/scripts/prepare-docs.mjs` rather than duplicated by hand, so a new file in `docs/` needs no edit here to appear at `/docs/`. - [x] **`cargo doc`'s output mounted at `/api/`**, and a redirect from `/api/` itself to the `didbot` facade crate, since a workspace doc build has no root page of its own. - [x] **`scripts/build-site.sh`** (cargo doc + astro build + the `/api/` mount + `site`'s own tests) **and `scripts/publish-site.sh`** (that, plus an upload of the build as an immutable `releases//` tree, the `tofu apply` that flips CloudFront's origin onto it, and the invalidation after it), with a `--dry-run` that plans, refusing to publish without credentials or a `404.html` in the tree. - [x] **`infra/site/`**: the S3 bucket, the CloudFront distribution and its certificate, the viewer-request function that resolves a directory index, and the `did.bot` records — the apex alias pair and the `_atproto` TXT record that makes the domain resolve as a handle. - [x] **Automated checks**: a clean build, a link checker over the built output (including `docs/`'s rewritten links and the `/api/` mount), a check that runs `infra/site/viewer-request.js` itself over every address the built pages link to and lands each one on a real object, the placeholder-coverage check above, a check that every file in `docs/` is reachable from the built site, a stubbed-AWS test of `publish-site.sh`'s dry run, its refusals, and the order it applies and invalidates in, third-party HTML validation of the pages this project actually authors, and a check that the wasm artifact exists, stays inside its size budget, and that the home page's real content survives with its `