chadtmiller.com/knotless
git that lives in your Atmosphere account
knotless site deploys a directory as a repository's website
MergedThis adds knotless site, which deploys a directory as the repository's website. It also adds the dev.knotless.site lexicon and record types, the handle-to-label encoding in crate::site, the pages design doc, the CLI reference entry and a changeset. I read all eleven files and found nothing that has to change. The checks come first, and nothing is uploaded before they pass: a file count of 1–2000, valid paths, and --fallback/--not-found naming real files. The refusals are covered. Blobs are reused by sha256 from the live record, and duplicate bytes upload once through the held map. An empty file gets no parts. The write is a create the first time and an update after. The label encoding round-trips, and the tests cover both directions.
Minor notes, none of them blocking:
valid_pathalso rejects empty,.and..segments, but the error always says "longer than a site's path can be". Only the length case is true. This is unlikely to come up, since the directory walk can't produce such segments.walkbuilds paths withto_string_lossy. Two non-UTF-8 file names could collapse to the same path, and the record needs distinct paths. A duplicate-path check or a refusal of non-UTF-8 names would close that.served_attrusts the handle in the URL as the owner's current handle. A stale handle in the URL would print an address that no longer serves. This is cosmetic, because the address is only printed.
Nothing was built or run here, so the 100% coverage gate is up to CI. The no name a site is served at branch in served_at looks like the line it could miss.
Since my last review, the only change is in the sites test. It now expects the output of the deploy and --down runs without the trailing newline: uploaded 3 of 5 files\n{address} and taken down. This looks like a fix for how the test harness returns output, and it changes no behavior. The fmt and clippy checks and the docs check pass. The test and e2e checks were still running when I looked, so the sites test result and the coverage gate are not yet confirmed. My earlier notes were minor and non-blocking, and nothing here changes them.
Since my last review, the only change is in served_at in crates/knotless/src/site.rs. with_context(|| format!(..)) became context(format!(..)). Behavior is the same. The string is now built even when a name is found, which costs next to nothing and probably removes an uncovered closure region for the coverage gate. My earlier minor notes still stand and none of them block. Checks on this head: the skipped workflows passed, and test, e2e, docs and check are still queued, so the sites test and the coverage gate are not confirmed yet.
What changes
knotless site <url> <dir>deploys a directory as the repository's website, as its owner. It walks the directory and checks every path, then reads the livedev.knotless.siterecord. Files whose bytes the live site already holds keep their blobs, and the rest are uploaded, split into parts over the PDS's limit as releases' files are. Then the record is written: created the first time, replaced after. It prints how many files it uploaded and the address the site is served at,https://<owner>.knotless.page/<repo>/.--fallback <path>and--not-found <path>name the single-page app page and the 404 page. Each must be one of the site's files.--commit(KNOTLESS_RUN_SHAin a run) and--checksay what built it.<dir>, it prints the live site: its address, file count, commit and time.--downdeletes the record.crate::sitehas the owner label, the handle with each-doubled and then each.a-, and reads it back. Serve will use that to answerknotless.page. A handle whose label won't fit falls back to thedid:plcid.Why
It's the client half of pages (docs/design/pages.md). Reusing the blobs of unchanged files keeps a deploy to what changed, since bsky.social allows 1,000 blob uploads a day. Each file gets its
contentTypefrom its extension at deploy, so serve never has to guess one.This is based on the design pull, Pages are designed, with the dev.knotless.site record, which is queued into feature/pages. Until that lands, this diff holds its commits too.
How it was checked
A new
sitestest in crates/knotless/tests/cli.rs covers:Unit tests cover the label encoding both ways, paths and content types. Nothing was built here: CI builds and runs it.
Shortcomings
Nothing serves the sites yet: that's the next pull, in knotless-serve. There's no MCP tool for it yet either.
🤖 Generated with Claude Code