Skip to content
All posts
4 min read

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.

Case studyCraftReading UX

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.