atmob paste#
A pastebin / GitHub-Gists clone built on the AT Protocol. Every paste is a public record in the author's own repository — no central database, portable across PDS hosts, and readable by any atproto client.
- Live: atmob.in
- Lexicons:
in.atmob.paste.* - Frontend: a dependency-vendored static SPA (no build server), deployable to any static host
What it does#
- Multi-file pastes ("bundles"). A bundle is an ordered list of files. Each
file is either small text stored inline, or a reference to a
standalone paste record — which itself is either inline text (
document) or an uploaded blob (any binary: images, archives, …). - Sign in with atproto OAuth. Users authorize against their own PDS; the app
only requests write access to its own
in.atmob.paste.*collections plus blob upload — not "access to everything." - Stars & comments. Standard atproto social records that live in the interacting user's repo and point back at the bundle via a strong reference.
- Public viewing, no login required. Any bundle is viewable by its
at://URI; the app resolves the author's PDS and renders it. - Markdown rendering. Bundle descriptions, comments, and any
.mdfile in a paste render as formatted markdown (files have a rendered/source toggle). Output is sanitized with DOMPurify, so untrusted content can't inject scripts.
Repository layout#
lexicons/in/atmob/paste/ Lexicon schemas (the source of truth for the data model)
defs.json shared vocab: visibility, language
document.json single inline-text paste
blob.json single blob-backed paste (binary/large files)
bundle.json the multi-file gist; inline files + references
comment.json a comment (in commenter's repo → strongRef to subject)
star.json a star (in star-er's repo → strongRef to subject)
web/ the static frontend (see web/README.md to deploy)
index.html markup + styles
app.js all app logic (ES module)
vendor/atproto.js self-contained bundle of the @atproto/* libs
vendor/build.sh regenerates vendor/atproto.js
client-metadata.json OAuth client identity (served publicly)
The data model#
Three "content" record types and one that ties them together, plus two social
records. All use tid record keys and carry a timezoned createdAt.
| Lexicon | Holds | Notes |
|---|---|---|
in.atmob.paste.document |
UTF-8 text, inline | For snippets ≤ ~64 KB |
in.atmob.paste.blob |
a blob (uploadBlob) |
For large or binary files |
in.atmob.paste.bundle |
an array of files | Each entry is an #inlineFile (text held in the bundle) or a #reference (strongRef to a document/blob) |
in.atmob.paste.comment |
text | subject strong-refs the target |
in.atmob.paste.star |
(nothing but the ref) | subject strong-refs the target; unstar = delete |
Why inline and blob?#
atproto records should stay small — a few dozen KB, ~1 MiB hard cap — with larger content living in blobs. So the model offers a gradient:
- a one-line snippet → a single
#inlineFilein a bundle (one record total) - a multi-file config gist → several
#inlineFileentries (still one record) - a large source file or an image → a
blob/documentrecord,#referenced - anything that needs its own shareable URL → a standalone
document/blob
The bundle's files array is an open union, so new entry variants can be
added later without breaking existing readers.
A note on "private" pastes#
Every record in a public PDS is fetchable by anyone with the URI. The
visibility field (public | unlisted) is therefore a display/indexing
hint, not access control. Genuinely private pastes would require client-side
encryption — deliberately out of scope here.
Discovery relies on network infrastructure#
A record in your PDS is public, but finding records scattered across the network needs infrastructure a static page can't provide itself:
- Stars & comments live in other people's repositories. The frontend reads counts and threads from Constellation, a network-wide backlink index. Your writes hit your PDS immediately; the aggregated count/thread catches up within seconds (the UI renders your own action optimistically in the meantime).
A global list of all pastes is intentionally not implemented: Constellation is backlinks-only and cannot enumerate a collection, and a live Jetstream feed only covers records seen while connected. Durable global discovery would require running an app-view — a firehose consumer that stores records in a database.
Running it#
The frontend is a static site with no build step at runtime — the atproto
libraries are pre-bundled into web/vendor/atproto.js. See
web/README.md for deploy and local-development instructions.
Quick local start:
cd web
python3 -m http.server 8080 # then open http://127.0.0.1:8080
To publish your lexicons so other atproto software can resolve them, create a
_lexicon DNS TXT record on paste.atmob.in pointing at your DID and write the
schemas as com.atproto.lexicon.schema records — see the
Lexicon resolution spec.
Built with#
- AT Protocol ·
@atproto/api·@atproto/oauth-client-browser - Constellation backlink index
- esbuild for vendoring