A link aggregator on the AT Protocol: tagged stories and threaded comments, with moderation in the open.
SocialRust · HTMLGitHub @shindakunOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Rusty link thing.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗A link aggregator for threaded.at: tagged stories, threaded comments, flags with reasons, user levels, AI-assisted triage, and a public moderation log. Sign-in is atproto OAuth. Submissions and comments are records in the author's own repo.
Working today:
THREADED_HYDRANT_SOCKET is set.Next: M5, reader features: an inbox for replies, "new" markers on threads, saved and hidden stories, tag filters, Atom feeds, posting to Bluesky, archive links, comment folding, and keyboard shortcuts. It is built and waits on a deploy and the checks done by hand. Its plan is docs/0008-m5.md. The milestone table is in the design doc, section 16.
Requires a stable Rust toolchain, from rustup.
make seed # demo tags and stories into threaded.db
make run # serve on http://127.0.0.1:8080
make check # fmt-check, clippy, test, audit, markdown lint, lexicon lint and validation
make a11y # axe-core audit of every page, light and dark, and the page scripts in Chrome
make hydrant # a local hydrant indexing at.threaded.dev.* records
make help lists every target. Some need tools beyond Rust:
| Target | Needs |
|---|---|
make audit | cargo-audit |
make md-lint | markdownlint-cli2 |
make lex-lint, make lex-validate | goat |
make a11y | Node and Chrome or Chromium (set CHROME if it is not in its default place); it installs puppeteer-core and axe-core itself |
make hooks | pre-commit |
make hydrant | a hydrant build at ../hydrant/target/release/hydrant (set HYDRANT for another path; building it is in docs/0006-m4.md). It keeps its database in ./hydrant.db and serves its API on ./hydrant.sock |
| Variable | Default |
|---|---|
THREADED_DB | sqlite:threaded.db |
THREADED_ADDR | 127.0.0.1:8080 |
THREADED_BLOG_DIR | blog |
THREADED_PUBLIC_URL | The public origin, such as https://threaded.at: the OAuth client ID and the absolute URLs in link previews. Unset: the localhost development OAuth client, and http://127.0.0.1: with the THREADED_ADDR port |
THREADED_OAUTH_KEY | required with THREADED_PUBLIC_URL |
THREADED_COOKIE_KEY | required with THREADED_PUBLIC_URL; unset in development means sessions end on restart |
THREADED_ANTHROPIC_KEY | a file holding an Anthropic API key; unset turns AI triage off |
THREADED_HYDRANT_SOCKET | hydrant's API socket, such as ./hydrant.sock from make hydrant; unset reads no records from the network |
THREADED_PLC_URL | https://plc.directory: where user review reads an account's creation time |
THREADED_TYPEAHEAD_URL | https://public.api.bsky.app: the AppView the sign-in typeahead asks |
RUST_LOG | info |
make run
Open http://127.0.0.1:8080, not localhost: atproto's development client
returns to 127.0.0.1. Sign in with any atproto account. threaded keygen DIR
writes the production key files.
A local build writes at.threaded.dev.submission, .comment, and .vote
records to your real repo; production writes at.threaded.*. Sign-in asks to
write submissions and comments. Publishing upvotes asks again, on
/settings, for vote records.
threaded tag add rust --description "The Rust language and its ecosystem"
threaded tag add meta --description "About Threaded itself" --boost -0.5
threaded tag list
tag add on an existing tag updates it. --boost is the tag's rank boost,
from -10 to 10. Both commands use THREADED_DB.
threaded role grant did:plc:... admin # or moderator; the account signs in once first
threaded role revoke did:plc:...
threaded triage check # one test item through the triage model
Roles take a DID, never a handle: a handle can pass to someone else. Admins
then work from /admin: tags, people, settings, and the site rules that AI
triage reads. AI triage runs only with THREADED_ANTHROPIC_KEY set and a
rules version saved; without either, moderation runs on flags and moderators.
Drop a Markdown file with YAML front matter into THREADED_BLOG_DIR and it is
live within a second, no restart. Editing or deleting it works the same way.
---
title: Sign-in is live
date: 2026-11-02
summary: Any atproto account can now sign in.
---
Body in CommonMark.
title and date (YYYY-MM-DD) are required. slug defaults to the file
name and cannot be feed, and draft: true hides a post. The full rules are in the design doc,
section 12. A file that fails to parse is skipped and the reason is logged.
| Route | Page |
|---|---|
/ | Front page |
/newest, /active | Newest, most recent discussion |
/top?t=day|week|month|year|all | Top by score |
/t/{tag} | Ranked within a tag |
/domain/{domain} | Newest from a domain and its subdomains |
/s/{short_id} | A story and its comments |
/s/{short_id}/edit, /s/{short_id}/delete | Edit or delete your story |
/s/{short_id}/bsky | Form target: post your story to Bluesky again after a failed try |
/c/{short_id}/reply, /c/{short_id}/edit, /c/{short_id}/delete | Reply to a comment; edit or delete yours |
/submit | Submit a story |
/comment, /vote, /flag | Form targets for comments, upvotes, and flags |
/u/{handle} | A user's stories, comments, and public upvotes; a DID works too |
/settings | Public upvotes, cross-posting to Bluesky, tag filters, keyboard shortcuts, and keeping your posts out of AI triage (form targets /settings/bsky, /settings/tags, /settings/keys, /settings/triage) |
/inbox | Comments on your stories and replies to your comments; the header shows the unread count |
/saved, /hidden | Your saved stories and the stories you hid; form targets /s/{short_id}/save and /s/{short_id}/hide |
/s/{short_id}/suggest | Suggest a story's title and tags |
/mod | Moderation queue: Holds, Flagged, Suggestions, Edits, Detached; form targets /mod/verdicts/{id} and /mod/suggestions/{id} |
/mod/s/{short_id}, /mod/c/{short_id}, |
src/main.rs CLI: serve, seed, keygen, tag, role, triage check
crates/core/ ranking, levels, policy and settings, search parser, links, tags
crates/auth/ atproto OAuth: keys, SQLite session store, sign-in flow, record writes
crates/store/ SQLite via sqlx; migrations/ at the repo root
crates/triage/ AI triage: the model request, verdicts, the worker
crates/indexer/ reading records from hydrant: the client for its socket API
crates/web/ axum router, askama templates, CSS
migrations/ forward-only SQL migrations
tests/ tests of the threaded command line
lexicons/ at.threaded.* lexicon schemas
scripts/a11y/ axe-core page audit and browser checks of the page scripts
blog/ blog posts, served from ./blog
deploy/ systemd units, nginx config, backup, update, and hydrant build scripts
docs/ design doc, deploy doc, milestone plans, and deck
third_party/ jacquard crates patched onto fixed crypto; see its README
Built on axum, askama, sqlx with SQLite, jacquard for atproto and OAuth, pulldown-cmark and ammonia for Markdown, and hydrant for reading records from the network.
MIT.
/mod/u/{did}Review a story, comment, or user, and act on it; a handle in /mod/u/ redirects to the DID |
/moderations | The public moderation log |
/moderations/stats | How often moderators agreed with AI triage |
/admin, /admin/rules, /admin/tags, /admin/people, /admin/settings | Health, site rules, tags, roles, and settings; form target /admin/breaker |
/search?q= | Search; syntax in the design doc, section 7 |
/tags | Tag list |
/blog | Blog index, newest first; ?page=2 and on |
/blog/{slug} | A blog post |
/login | Sign in with an atproto account |
/oauth-client-metadata.json, /oauth/jwks.json | OAuth client metadata and public key |
/feed, /newest/feed, /t/{tag}/feed, /domain/{domain}/feed, /u/{did}/feed, /blog/feed | Atom feeds, 30 entries each; a handle in /u/ redirects to the DID |
/healthz | Liveness |