!░░│¡░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒╠
!░!)φ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╠╠░░▒╠
]░░╟╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▒]░▒╠
]░░╟╣▓╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╣▓▓▓▓▓▓▓█▒]▒▒╡
]░░╟╣▓╬╬╬╬╬╬╬╬╬╬╬╬╬╬╣╣╣▓▓▓▓▓▓▓▓▓▒▐▒▒╡
]░░╟╣▓╬╬╬╬╬╬╬╬╬╬╬╬╬╣╣▓▓▓▓▓▓▓▓▓▓█▒▐▒▒╡
]░░╟╣▓╣╬╬╬╬╬╣╬╣╣╣╣╣▓▓▓▓▓▓▓▓▓▓▓██▒▐▒╠╡
[░░╟╣▓▓▓╣╣╣╣╣▓╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▒▐▒╠╡
[░░╟╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▒▐▒╠╡
]░░╟╣█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█████▒▐▒╠╡
[░░╟╣█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████⌐▐╠╠╡
[░░╚╙└└└└└└└╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙╙ ▐╠╠╡
[░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╠▒▒╡
[░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░▒▒▒▒▒▒▒╡
[░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒╠╡
Γ░░ΓΓΓΓ░╚╚╚░╚╚╚╚╚╚▀▀▀▀▀▀▀▀▀▀▀▀▀▀╬╠╠╠Γ
[░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒╠╠╡
φ░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒╠╠╠╠╡
▐░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒╠╠╠Γ
]╠╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬⌐
_____]╚╠╣╣╣╣╣▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓⌐____
_ⁿƒⁿ╙]M░ƒ∩░%∩░╔∩░╔∩⌐≤░░╦ƒ░MƒƒM5²MδƒMΘφM░j∩>⌐%^^(φƒ╕░ƒ╕░ƒp░░Q░
.7DDφ≥▒5≥▒φ≥▒δUΓφ7Γê≤Üê5Üφ5Üδ≤Üê5▒φ╠▒φ╠⌂Då░5╠░_▐"S║"S║"▒║""Å░"ⁿⁿ-.,
. ≈╚╚╚╚δ╚╚δδδ╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚╚W╚╚⌐╚╚W╚╚⌐╚╚Ü.-W╚╚╚╚╚ë╚╚ë╚╚Θ░ __`-
=Q░░░░░░░░░░░░░░░░░░░░░░▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╝
Plain Text Nostr
My latest project is live at plaintextnostr.com.
I wanted a reader that felt small, quick, and easy to scan—closer to a normal website than a heavy client. plaintextsports.com was the reference I kept in my head for that kind of straight-ahead simplicity. I also wanted threads you can read in a linear, Twitter-style view or a nested, Reddit-style view, and switch between them on the fly.
Technically, it is a Go app that renders HTML on the server, uses a little vanilla JavaScript and CSS in the browser, and never asks the browser to open relay connections itself. Your keys stay in the browser; the server talks to relays, keeps a local copy of events it has already fetched, and tops up missing data in the background so pages stay snappy. Feeds can stick to people you follow, or you can widen them with an optional “web of trust” filter (a few hops out from your follows). If you are not logged in, you see a curated slice of the network plus trending—not the entire global feed.
Data on disk
The server keeps almost everything in SQLite: raw notes, tags, which relay saw which event, and derived tables for profiles, follows, reply counts, trending, bookmarks, and similar. A second small database (bbolt) stores only the follow graph used for web-of-trust: who follows whom, updated whenever contact lists change in SQLite. Pages are drawn from that local store first; relays are used to refresh and extend what is missing.
When you load a page
You only use normal HTTP and cookies. The server figures out who you are (if anyone), which authors belong in your feed (follows, optionally the trust-expanded set), and renders from the cache. Stale or missing pieces get queued for background fetches so the request itself does not sit waiting on every relay.
Talking to relays
The server contacts a bounded handful of relays per operation, with timeouts and backoff so one bad relay does not slow everything down. It picks relays using hints from profiles and lists where it can, then falls back to defaults or relays you have configured. Events are deduplicated and checked before they are written. Old rows can be pruned when the cache grows too large, with sensible handling of updated profiles and follow lists so you always see the latest version for a given person.
Signing in
Private keys never leave the browser—you can use a read-only public key, a browser extension signer, a throwaway or session key, or (for now) save a remote-signer connection as a session boundary without the full remote-signing wire-up yet. Publishing is intentionally limited to a small set of note types; the README lists what is in scope today.
The app is Go, HTML templates, vanilla JavaScript, and plain CSS—no single-page app shell. For package layout, environment variables, negentropy toggles, debug endpoints, and deployment, the project README still has the full detail.