Avoidant Book: a reading app for exactly one chapter
A calm, mobile-first reader built around a single therapeutic text treated as sacrosanct — born as a chat artifact and ported to production-ready Next.js the same afternoon.
Some products are a platform; this one is a single chapter. Avoidant Book is a mobile-first reading app whose entire content is one long-form therapeutic text, written in the Ericksonian storytelling tradition and kept anonymous. The app's brief was humility: the code promises the chapter is “included verbatim, without modifying a single word,” and every design decision follows from treating the text as the client. This write-up extends the same respect — we describe the reader, not the chapter.
- Source lines
- 1,660
- App component
- 1
- localStorage keys
- 5
- Minutes to port
- <90
One component, whole product
The product is one 1,033-line client component wrapping the text in everything a long read needs: a section-per-page reader with fade-slide transitions, a sticky translucent header carrying the current section and a spring-animated progress bar, prev/next controls in three placements (table of contents, oversized mobile buttons under the text, desktop footer), and iOS-style bottom sheets for the TOC and settings on mobile — plus a floating thumb-reach action bar that never blocks scroll.
The controls respect the reader: full-text search filters the section list itself, so the TOC, position counters, and prev/next all operate on the filtered set; a “key parts” toggle narrows to exercises and core segments; a font-size slider and compact mode persist across visits; and the last-read section survives reloads — five localStorage keys, no accounts, no network calls, no analytics. The chrome is deliberately colorless: zinc palette, dark by default, nothing competing with the text.
The canary in the template literal
The chapter lives in the code as a single ~2,000-word template literal, which creates a failure mode most apps never face: a bad paste or an overzealous find-and-replace can silently corrupt it. So the app ships five self-tests that run on mount — section parsing yields sections, expected titles exist, a known exact phrase from inside the text still exists (the canary), and the markdown renderer returns valid elements in both spacing modes. The renderer itself is a custom ~130-line function that builds React nodes directly — bold, italics, lists, blockquotes — with zero dangerouslySetInnerHTML, so it's XSS-safe by construction.
Under ninety minutes to port
The git history is two commits on the same December day: a single-file artifact from an AI chat session, then a Next.js port 84 minutes later — with only 51 lines changed in the move. That's the studio's tooling thesis in miniature: a well-formed artifact is already production-adjacent, and the distance from “works in a chat window” to “deployed with a URL” is measured in minutes when the code was structured honestly to begin with.
Honest edges
- The page metadata still carries the scaffold's defaults — a real gap for a public deployment, and first on the fix list.
- Dark mode applies after hydration, so a cold load flashes light for a frame.
- The key-parts filter and the self-test canary string-match exact chapter wording — and one filter condition already matches nothing, proving the point.
- The progress bar lacks a progressbar role, and the section transitions don't yet honor reduced motion.
When the content is the client, the interface's job is to disappear — and the engineering's job is to verify the text arrives intact.
What we'd tell a client
- Single-purpose apps earn disproportionate polish — one chapter with great controls beats a library with mediocre ones.
- If content is embedded in code, test the embedding: a canary phrase is one line and detects the silent-corruption class of bugs.
- Building a markdown renderer that constructs nodes directly is often simpler and safer than sanitizing HTML.
- Respecting a text can be an engineering requirement — 'verbatim, unmodified' is a spec, and it's testable.