chadtmiller.com/knotless
git that lives in your Atmosphere account
A repository's commits, tags and releases as Atom feeds
MergedThis adds commits.atom, tags.atom and releases.atom per repository. The feeds are hand-written XML, read as the viewer like the raw route, with <link rel="alternate"> discovery on the pages, plus docs and tests. The structure is sound and the docs are updated. The checks were all still queued when I looked, so I could not confirm that test and its 100% coverage gate pass.
One thing must change: tag entries use the commit's page as their Atom id. Two tags on the same commit, such as v1.0 and latest, then get the same id inside one feed. Atom requires entry ids to be unique, and readers collapse such entries into one, so one of the tags is dropped. A tag that moves to a new commit also looks like a new entry, but that is arguably fine. Give each entry an id that includes the tag name, for example tag:<host>,<repo>:<name> or the tree link at the tag. Then add a test with two tags on one commit.
Smaller point, not blocking: xml() keeps U+FFFE and U+FFFF, which XML 1.0 forbids. They are rare, but the doc comment says it leaves out what XML can't hold.
Both points from my first review are done. Tag entries now take their id and link from the tree at the tag name, so two tags on one commit stay two entries. The browsing test checks v1 and v2, which tag one commit, and expects a separate id for each. xml() now also drops U+FFFE and U+FFFF, and its unit test covers them. The docs in serve.md and api.md describe the tag ids. I read the code and tests but did not see the checks run, so the test job and its coverage gate are still unconfirmed.
This round only merges main into the branch and resolves the conflicts in the docs, the tests, vite.config.ts and the tags page. I read the combined diff and found nothing that the merge broke. Tag ids are still taken from the tag name, and xml() still drops U+FFFE and U+FFFF. The serve.md, access.md, api.md and development.md text, the dev proxy rule for the .atom paths, and the feed="tags" link on the tags page next to the existing archives prop are all in place. The browsing, empty-repo, releases and gateway tests are intact. The checks on this head were all still queued when I looked, so I haven't seen the test job or its coverage gate pass. The queue run before landing will confirm that.
8 passed, 2 skipped
What changes
Atom feeds. Every repository now has three feeds for feed readers:
/<owner>/<repo>/commits.atom: the newest 30 commits on the default branch, each by its author, with its message as text and its id its commit page./<owner>/<repo>/tags.atom: the newest 30 tags that name a commit, with that commit's subject, linked to the tree at the tag./<owner>/<repo>/releases.atom: the newest 30 releases, with their notes drawn as HTML as the releases page draws them, each linked to its package's releases and named by its record's URI.All links are absolute, built from the request's origin. Like the raw route, a feed reads as the viewer: a private repository's feed goes to its members only and isn't cached, and a reader with no session gets 404. A feed with nothing in it is still a valid feed, dated at the epoch.
Discovery. The commits, tags and releases pages each name their feed in a
<link rel="alternate" type="application/atom+xml">, where readers and browser extensions look. Nothing visible changes on the pages.Why
This is the second easy item on Basic forge features missing, from archive downloads to webhooks. People follow releases and commits through feed readers, and GitHub, GitLab and Forgejo all offer these feeds.
It builds on A revision's files download as one tar.gz or zip archive, the archive downloads. That pull's commit is in this branch because the two touch the same docs lines and tests, so queue this one behind it.
I wrote the XML by hand, escaping text and leaving out the control characters XML can't hold, rather than adding a crate. There are three element kinds, and the escaping is unit-tested.
How it was checked
browsing_a_repository(tests/web.rs) checks:private, no-storefor a member, varying by cookie, and 404 with no session.npm run checkpasses. I didn't build the Rust or run the tests locally; that's left to the checks.Shortcomings
/<handle>.atombeside/<handle>, and the page has no other natural path for it.🤖 Generated with Claude Code