Longbox
liveComic reading-order checklists that sync across every device.
The problem
Long comic runs come with a reading order — which issues, in which sequence, and which ones you can skip. They usually live as a forum post or a blog list, and you work through them over months.
That is a bad fit for a checklist in a browser. Tick things off on a laptop and the phone knows nothing about it. Reading happens on the couch, in bed, on a train, and progress needs to be in all three places or it is not progress, it is a note you lost.
The second problem is quieter and more annoying. A reading order names issues, and a library holds books. Avengers #35-44 is one line in a list and a completely different question when you are standing in front of a shelf trying to work out which volume that is, especially when the same title has been relaunched three times.
What it is
A single page and a small server. It holds several reading orders, switchable from a dropdown, and every one of them is editable from the page itself — add an entry, rename one, reorder a section, retitle the whole thing.
Each section can carry a collected editions note: which trade or omnibus covers it, and the year. It sits in its own field and renders in mono under the description, because it is reference data you scan while hunting for a book rather than prose you read.
- —Several lists at once, each with its own progress
- —Every list owns its own vocabulary — title, intro, and what the required and optional halves are called
- —Import and export as JSON, or copy one of the bundled examples
- —Undo, twenty revisions deep, including the ticks a deletion took with it
- —Filter, hide-ticked, and read-only links for handing someone a list
Two documents, two concurrency models
The same disagreement, resolved two different ways depending on which document it lands in.
The interesting part is what happens when two devices disagree, and the answer is different for the two things being stored.
Ticks change constantly and from anywhere, so they travel as individual operations carrying a timestamp, and the server merges them per issue with the newest edit winning. The obvious alternative — save the whole set of ticks — is actively worse than doing nothing: a phone that had the page open with a week-old view would push that view over everything read since.
The list itself changes rarely and deliberately, so it is written whole and guarded by a revision number. An editor holding an old copy is refused outright and told to reload, rather than having its idea of the list merged into everyone else's.
The bit that made it possible
Ticks are keyed by an id, and that id is frozen for the life of an entry. It used to be derived from the entry's text, which was fine while the list was hardcoded and would have been quietly destructive the moment it became editable: every rename would have orphaned its own tick, silently, with no error anywhere.
Freezing the ids is what lets you rename an issue, renumber it, or move it to another section and keep the fact that you read it. Deleting an entry deletes its tick with it, so a reused id can never inherit a dead one — and undo puts both back together, because a restore that returned forty rows and marked them all unread would not be an undo.
Writing a list, including with a model
For anything longer than a few entries it is faster to write JSON than to click, so the app documents its own format: an authoring guide and a JSON Schema, both served by the app itself so they are wherever it is running.
Import is deliberately forgiving, because the strictness belongs at the door rather than in the store. Ids can be left out entirely and are generated. Reasonable synonyms are accepted — series for s, issue for i, entries for items — and a bare string is read as a title. Whatever comes out of that is then handed to the same strict validator the app uses on itself, so nothing malformed is ever stored.
There is a dry-run endpoint for exactly this: post a document and it reports what it would create, how many ids it would invent, and which sections came out empty, without saving anything.
The guide also carries a prompt for generating a list with a model, and is blunt about what to distrust in the result. Which volume collects which issues is stated confidently and wrongly more often than anything else, and it is precisely the detail you lean on in front of a shelf. The running order is an editorial opinion too, not a fact to be recalled.
What it runs on
One Node process with no npm dependencies — node: builtins only, no build step, no lockfile, nothing to keep patched. The whole app is one server file and a directory of static assets.
It runs in a container on my own hardware, behind the reverse proxy that fronts the rest of my services, with its data on a bind mount like everything else. The container has a read-only root filesystem and no capabilities.
There is no authentication, on purpose: it is a list of comics, and the network is the gate. The read-only share links hide the interface but are not a permission, and the README says so plainly rather than implying otherwise.
The example it ships with
A fresh install is not blank. It comes with one worked list — Jonathan Hickman's Fantastic Four and Avengers, the event they build to, the tie-ins worth the time, and the books that pick up the threads afterwards. Eleven sections, 192 entries.
Every section is annotated with which collection covers it and in what year, including the places where the trades disagree with the reading order, and one entry where I could not confirm which volume carries two issues and the note says to check rather than guessing.