atproto git client
README.md

www #

atgc.codes: an Astro site, with the parts only the binary knows generated into it.

cd www
npm install
npm run dev      # emit the assets, then serve on 127.0.0.1:4321
npm run build    # emit the assets, build to www/dist, then verify it
npm test         # the scripts' and lib's own tests

npm run dev and npm run build both run npm run assets first, which is cargo run --features www -- www-assets --out src/generated.

The dev server binds loopback and takes its port strictly: several agents share this machine, and a server that quietly moved to the next free port would have one of them reading somebody else's site.

Who owns what #

Every word on the site is in src/pages/ and the components beside it. Change the copy, change a heading, add a card, write a note: no Rust, no rebuild of anything.

src/generated/ is written by the binary on every build and is ignored by git. It holds only what the page cannot be trusted to state for itself:

File Why the binary has to say it
facts.json The version cargo release computed, and the install line cargo test asserts against the README's
theme.css, field.css, mark.svg The palette, the same bytes the login page atgc serves is built from
field.html Nineteen thousand cells of generated art, from crate::art
terminal.html atgc about's own drawing, not a screenshot of it
fonts/ Comic Neue, vendored in src/html/site/fonts/

Updates #

The blog. Two names, both in src/lib/feed.ts: the menu says updates, because a nav item has one job and a reader should know where a link goes before clicking it. The publication is genetic drafts @ atgc.codes. Neither is explained on the page.

A note is a markdown file in src/content/posts/, with frontmatter the schema in src/content.config.ts checks:

---
title: What changed
description: One line, and it is what the feed and the index show.
publishDate: 2026-09-02
tags: [atgc, release]
draft: false
---

Drafts render under npm run dev and are left out of a build. The rule is import.meta.env.DEV rather than a flag, so there is no way to ship one by forgetting to turn something back on.

/posts lists them, /posts/<slug> is one, and /atom.xml is the feed — hand-written, because the whole of Atom this needs is one entry per note and a feed is a thing you have to be able to read to trust.

Publishing to atproto #

Each note can also be a site.standard.document record in the publishing account's own repository, the way substandard.blog does it:

ATP_IDENTIFIER=… ATP_APP_PASSWORD=… npm run publish:notes -- --dry-run

sequoia.json configures it. It still has a PLACEHOLDER publicationUri — create the publication once with sequoia login && sequoia init, then put its at:// URI there. That is the only place it goes: the two things standard.site reads are both generated from it.

  • <link rel="site.standard.publication"> in every page's head, and <link rel="site.standard.document"> on a note that has been published. sequoia injects whichever a built page is missing; emitting them from the source instead means it never rewrites dist/, so what shipped is what was built.
  • /.well-known/site.standard.publication, served by a route rather than checked in under public/.

scripts/publish.sh refuses to run while the placeholder is there, and verify:dist checks both once it is gone.

sequoia init asks for the publication name at a prompt rather than reading it from the config, so give it exactly:

genetic drafts @ atgc.codes

That is PUBLICATION in src/lib/feed.ts, which is also what the Atom feed calls itself — so a reader who subscribes and a reader who follows the record see the same name. verify:dist fails if the feed's title stops naming the host it is served from.

Publishing is the last step of a deploy, never part of one. A document record says "this note is at this address", so the address has to answer first; the script fetches every URL it is about to claim and checks the canonical link before writing anything. ATP_APP_PASSWORD is an app password, never an account password, and is never printed.

Once a note is published, sequoia writes its at:// URI back into the frontmatter — commit that, and the page starts carrying a rel="site.standard.document" link beside its canonical one.

Deploying #

AWS_PROFILE=jmm-atgc-codes-admin scripts/deploy.sh

That builds, uploads the tree to s3://atgc.codes/releases/<sha>/ and runs tofu apply to point CloudFront at it. A release tree is never rewritten, so a deploy is one pointer move rather than a bucket edited under a reader, and a rollback is the same apply with a sha already uploaded:

tofu -chdir=../infra apply -var release_sha=<sha>

../infra holds the hosting itself. Three things about it are worth knowing before you apply:

  • The zone is read, not created. It was made with the account and its records belong to this repository, so a tofu destroy here cannot take the delegation down with the site.
  • The domain is delegated and the certificate is issued. If that ever stops being true — a zone rebuilt, a registrar moved — ACM cannot see the validation record an apply writes and the apply sits in certificate validation until it times out. tofu -chdir=../infra output zone_name_servers says what the registrar's four NS records should be, and answers without applying anything, because the zone is read.
  • deploy.sh refuses to run against the wrong account. Credentials that work but belong elsewhere are the failure worth catching: the SSO sessions share one start URL, so a login authenticates whoever the browser is already signed in as and returns a valid token under whatever profile was asked for.

A miss is answered with the site's own /404.html. The bucket is private, so S3 reports a key it does not hold as 403 rather than 404, and CloudFront will not remap a status without a page to serve alongside it — verify:dist fails a build that does not emit one, because a release tree without it would send every typo to CloudFront's own error page.

The distribution sends a default-src 'self' CSP, which the site already keeps: it fetches nothing from anywhere else, and verify:dist fails a build that reaches a third party. The one inline script is allowed by a hash computed from src/scripts/site.js itself, and verify:dist fails a build whose inlined script is not that file — a stale hash would kill the script at the edge with nothing visibly wrong in the page.

One look, two surfaces #

atgc auth login serves a page over loopback while it finishes, and that page and this site have to look like the same project. They do not merely resemble each other: html::brand holds one palette, one mark and one field stylesheet, the loopback pages compile it in, and www-assets writes the identical bytes out for Astro. There is no second copy of --a anywhere, and the_site_and_the_login_page_share_one_look fails if one appears.

Rules #

The page states no fact it cannot be held to. The install line and the version come from facts.json. A Rust test walks these sources and fails if an .astro file spells either of them out itself; npm run build then runs scripts/verify-dist.mjs, which fails if the built site is missing the generated half, ships a font nothing references, reaches a third party, or serves a note at an address its feed and its record would disagree with.

Nobody's dependency. No atgc command fetches this page, no build needs it, and no error message sends anybody here for a fix.

Same-origin only. No font CDN, no analytics, no third-party anything: the fonts and the favicon are served from here. Not a rule handed down from anywhere — just worth keeping, since there is then no CDN to be down and nothing that learns who read the page except the host serving it.

Not documentation. --help has the flags, docs/ has the model, and git log has what changed. A feature here is one line and a command name. A landing page that starts explaining pull request semantics has turned into the worst copy of docs/architecture.md in the tree. A note may explain things at length — that is what it is for.