My book has a linter that yells at me for hyphenating âopen source.â1 It runs ~5,500 automated checks, rebuilds five formats on every git push, and fails the build if I so much as imply I still work at a job I left. For a book. That one person wrote.

I didnât set out to do this. Most people start a writing project by firing up Word or Google Docs, and I started down that same path. I even tried some purpose-built authoring tools, but every tool felt inferior to the ones I used as a developer every day. I did âthe only reasonableâ thing and threw all of them out, writing the whole book on Git, Markdown, and a CI build pipeline. If youâve read how I over-engineered my home networkâââtwiceââânone of this will surprise you.

Iâve been making websites for decades, so the leap was short: the same tools that build websites could build books, no last-minute magic-trick reveal required to abracadabra a standard Word doc into a full-blown book. There were missteps,2 but in the end, I would not have written Open and Async any other way. Hereâs how:

Naturally, the content itself lived as Markdown files in a Git repository. After all, thatâs where I spend most of my day. I used VS Code, with a handful of prose extensions (listed below). Each chapter was its own Markdown file, and a single index.yml file defined the order, making it easy to re-order chapters or add new ones.

Practically, I wrote most of this book on an iPadâââCodespaces in a browser tab, a Bluetooth keyboard, often nights and weekends while away from my deskâââand the Git repository kept everything in sync no matter where I opened it. I could focus on the words, and a bad idea was one git revert away from gone.

Not to mention, I had real-time feedback on my writing right in my IDE from the various prose linters, just as I would have real-time feedback on my code from ESLint or Prettier.

With content as code, the next logical stepâââand the point where âreasonableâ quietly left the buildingâââwas to set up automated tests. Testing prose the way you test code is something Iâd argued for years; this was me taking it to an absurd extreme. I did that two ways: real-time, and on push (CI).

Locally, as I typed, I ran several VS Code extensions all giving me real-time feedback. Specifically:

  • MarkdownlintâââMarkdown syntax and formatting consistency
  • Harper3âââgrammar and word choice, entirely on-device
  • LanguageToolâââgrammar, punctuation, and style4
  • Valeâââmy own house-style rules and banned terms
  • Alexâââinsensitive or exclusionary phrasing
  • Write-goodâââweak prose: passive voice, weasel words, clichés

All six also ran in CI (Alex and Write-good folded into Vale there; more on that below). Together, they layered hundreds of curated style rules over a full grammar engineâââall of it underlining my mistakes in real time, the way a red squiggle flags a type error.

In addition to running those open source linters in CI (some blocking), I built a custom test suite of my own: a standalone Node script of content validators, a Vitest suite, and Playwright specs. The validators are the fun part. Each is a few lines that read the Markdown and push an error with a file-and-line pointer. My favorite: I donât work at GitHub anymore, so any sentence claiming I still do fails the build.

That validator exists because I made the mistake onceâââwhich is the whole pattern. The first time an error got past me, I didnât just fix that one sentence; I wrote a rule so Iâd never have to catch it by eye again. Itâs cattle, not pets for prose: donât hand-nurse each chapter, govern the whole herd with policy. A mistake caught once becomes a check that sweeps every chapter and fails the build if it ever wanders back. Thatâs one of about thirty. Others Iâm proud of:

  • validateOpenSourceHyphenation
  • validateHypotheticalHooks
  • validateSentenceStarters
  • validateNoBareUrlLinkText
  • validateCalloutBalance
  • validateCrossReferences- [text](#anchor)cross-reference resolves to a real heading.

â¦plus a couple dozen more for small caps, em-dashes, doubled words, en-dash ranges, TL;DR length, and every other tic I could name.5

One of these caught me. validateHypotheticalHooks flagged the opening of a paragraph I was certain Iâd written myselfâââand I had. But reading it back cold, it did sound ghost-authored; Iâd absorbed the cadence from reading too much generated text and produced a fluent imitation of nothing. A linter canât tell good prose from bad. What it can flag are the patterns you reach for when youâve stopped thinkingâââwhich is exactly the thing you canât see in your own draft. Same reason eslint earns its keep: it canât tell good code from bad either, but it catches the autopilot mistakes your own eye skates right over.

All in all, the CI suite ran ~70 test files with 2,204 test cases and ~3,900 expect() assertionsâââplus another ~1,600 per-chapter structural checks from the validators above.

The linters caught mistakes, but they couldnât tell me whether the book repeated itself or whether a chapter was any good. For that I built a second layer of tooling: audits that judge the writing, not just check it. One thing they never did, though, was write it. Every word is mine; these tools are readers of last resortâââcatching what Iâd stopped being able to see.

After reading the book over and over, I was convinced Iâd repeated the same idea across chapters. I wanted proof, not a hunchâââso I built three layers of duplication detection, each catching what the one before it misses:

  • jscpd- 6
  • n-gramâââtokenizes every chapter in- index.yml, strips Markdown/Pandoc syntax, builds word n-grams (phrases of n words), and flags phrases appearing in more than one chapter (plus a âmost-repeated stock wordingâ ranking). Two modes: cross-chapter (default- --n) and intra-chapter (- --scope=intra --n=8) for a chapter repeating itself.
  • semanticâââthe one the other two canât do: the same- pointrestated in different words. An on-demand LLM audit, designed so it never feeds the whole book to a modelâââthree passes, each over small units:- intraâââone call per chapter: âwhere does this chapter restate itself?â
  • crossâââa single call over every chapterâs TL;DR, producing a map of conceptually overlapping chapters. A whole-book scan for the cost of one call.
  • argumentsâââextract each chapterâs load-bearing claims one call at a time, then cluster the same argument across all chapters in one final call. Catches arguments made in body prose that- crossmisses.

Was I actually repeating myself? By this final pass the two mechanical layers came up cleanâââbut theyâd earned it: jscpd and the n-gram scan had already caught the copy-paste and recycled phrasing (a doubled motif here, a reused TL;DR structure there), and Iâd fixed each.

What neither could see was the subtler kindââânothing was duplicated word-for-word anymore, just the same point in different words. The semantic pass caught that, and it was right: Iâd made the same argument, that moving office habits online isnât the same as working remote-first, in five separate chapters, on top of 166 smaller self-restatements scattered across 51 chapters. Iâd paraphrased myself too well for anything cheaper than an LLM to catch me. (share this quote)

My hunch was correct; Iâd just needed three escalating tools to prove what re-reading my own book to blurry-eyed exhaustion couldnât.

Beyond duplication, a second set of tools graded the prose itselfâââsplit by how they judge. Some are probabilistic (an LLM reads the chapter and forms an opinion; run it twice and the findings can shift), and some are deterministic (rules and arithmeticâââsame input, same output, every time).

The Claims lens paid for itself early. It stopped on a sentence claiming GitHubâs monthly all-hands âdedicated roughly half of each session to live Q&Aââââa number I was certain of and had wrong; it was closer to a third, and the share moved around over the years. No linter flags that: itâs clean, confident prose that happens to be false. Only a reader asking âis this actually true?â catches it, and Iâd have shipped it otherwise.

Claims is one of 20 single-purpose lenses in a âprose auditsâ testâââa per-chapter LLM auditor where each lens asks one narrow question so the model canât hand-wave a vague âlooks good.â --lens=all runs every lens; --models/--rounds add a deduplicated multi-model and self-consistency panel, so a finding has to survive more than one model (or more than one run) to count. A sampling of the rest:7

| Lens | What it catches |
|---|---|
| AI-tells | Phrasing that reads as machine-generated rather than my voice |
| Hook | Whether the opening earns its place or is throat-clearing |
| Legal | Legal or reputational risk (naming names, unverified claims) |
| Dated | Perishable references that will age badly (ârecently,â âthis yearâ) |
| Global | Idioms and cultural assumptions that trip up non-US readers |
| Dual-audience | Whether a chapter serves managers andindividual contributors |
| Promise | Whether the chapter delivers on the bookâs core promise |

Separately, I also had a traits analysis test, which scored each chapter on persuasion and engagement traits, catching prose that was technically clean but flat so I could give it another (human) pass.

I ran these after a round of editing, to see whether the book was improving as a reading experience: chapter- and paragraph-length distributions, whole-book reading time, a hard EPUB size budget (Kindle penalizes oversized files on delivery), a book-wide consistency checker, and a stats dashboard whose --check mode fails the build unless every âattention itemââââa chapter missing a TL;DR, an unbalanced calloutâââsits at zero.8

Together, these turned âis the book actually getting better?â from a gut feeling into a number I could watch move between drafts.

I am far from a designer, and Iâd never published an ebook beforeâââI had no idea how they worked beyond reading many of them. Two aha moments changed the way I thought about publishing:

  • An ebook is a website in a trench coat(share this quote)âââebooks are just HTML and CSS, albeit a very stripped-down version.- 9If you can make a website, you can make an ebook.
  • A print book can be one too, with enough effortâââCSS natively has powerful @media printand@pagerules, including left and right page styling, title pages, page numbers, and more.

For the interior design, I reached for Tailwind CSSââânot because itâs built for books (it isnât), but because itâs what I already use every day. @tailwindcss/typography gave me a typographic baseline to start from instead of a blank page.

One note: I purposefully hired a human designer for the cover. For a book about being authentic, the first impression had to be authentic.10

Styling every format from one stylesheet has a failure mode: CSS meant for one format bleeding into another. To keep my e-reader tweaks away from the paperback, I scoped each of them to @media not screen. But WeasyPrintâââthe HTML/CSS-to-PDF engine that draws the paperback, more on it belowââârenders the print PDF with media type print, and print is ânot screen,â exactly as the spec says. So two of those e-reader rules matched anyway and quietly bled into the book: a tighter line-height on callout paragraphs, and a box-decoration-break change that dropped the repeated padding on any callout that split across a page.

Neither was visible. Nothing looked broken. But each shaved a sliver of vertical space off every callout, and the book slowly deflated from 576 printed pages to 567. I only found out because the print PDF is pinned to exactly 576 pages11 and the build went redâââa page-count gate written to protect the cover spine, catching a CSS bug it was never designed for. Two formats from one stylesheet is a gift, right up until the cascade forgets which format itâs in. (share this quote) The fix re-pins the original values in a print-only block, loud with !important so the leak canât win.

The design and layout got a test suite as wellâââdriven by Playwright against the built HTML, so it checks what actually renders, not what I hoped the CSS did. Two categories:

The axe-core engine runs over the rendered book in both light and dark mode against WCAG 2.1 AAâââno critical or serious violations, AA color contrast in either scheme, alt text on every image, logical heading order, discernible link text (no bare âclick hereâ), and a declared lang attribute so screen readers pick the right pronunciation. Itâs the test:a11y gate, enforced in CI as validate-playwright. Accessibility earned more than a subsection hereâââit has its own accessibility statement, and a dedicated deep dive is coming.

These assert that the actual computed styles match the design intentâââthe details that break quietly and never show up in a diff. A representative few: the title page renders centered with a lighter-weight subtitle; the table of contents is a real nav with the doc-toc role and working links; every callout type carries its own unique left-border color and auto-generated label prefix; and small caps get real font-variant: small-caps, not just a class name.12

None of this is glamorous. Itâs exactly the kind of thing that breaks silently in a Friday CSS refactor and doesnât surface until a reader emails you.

Weâve got clean Markdown, and a test suite that ensures the content is clean, but we still need to get it into a format that can be published (EPUB, Kindle, paperback in two flavors, and web). Core to that custom build pipeline was Pandoc, a universal document converter that can read Markdown and spit out just about anything else. I used Pandoc as the engine, but Pandoc alone gets you a generic documentâââturning that into five store-ready formats took a pile of custom tooling:

Pandoc is only the engine. The full build also needs WeasyPrint to draw the PDF, Ghostscript to convert it, and a pile of Noto fonts for full Unicode coverageâââa gnarly stack of native dependencies to install by hand. The whole thing therefore lives in a Docker image and a dev container, pinned to an exact renderer version (a detail that matters more than youâd thinkâââsee the page-count gate below). That container is also why I could draft the book from an iPad: the heavy toolchain ran in Codespaces, not on my lap.

Everything starts on a throwaway copy of the source tree, so these transforms never touch the real chapters. A couple of scripts massage that copy before Pandoc runsâââstamping each build with its commit SHA, turning inline links into numbered footnotes for print (a hyperlink is useless on paper; a footnote with the full URL isnât), and reshuffling the chapters per format so the EPUB gets shareable quote links while print and Kindle get a QR share page instead.13

One Tailwind stylesheet (src/style.css) compiles through PostCSS to dist/style.css, so screen, print, and EPUB all start from the same source of truth. Each format then takes a different slice: strip-page-rules.js derives a cut-down stylesheet for EPUB, stripping the CSS Paged-Media features (@page, target-counter(), oklch(), custom properties) that Kindle and EPUBCheck choke on.

This is the part I didnât expect to love. Pandoc parses everything into an abstract syntax tree (AST) and lets you rewrite that tree with small Lua scripts before it rendersâââso format-specific tweaks live in code, not smeared through the prose. Deleting a node, for instance, is just returning an empty table:

The rest are variations on that idea:

  • add-div-titles.lua
  • Emoji, handled three waysâââcolor emoji render fine on screen, but- strip-emoji-kindle.luaswaps them for Kindle-safe glyphs (e-ink has no emoji font) while- body-emoji-images.luaturns them into inline Twemoji images for the print PDF.
  • tagline-share-links.lua

Turning emoji into images solved the missing-font problem and created a subtler one. Twemojiâs PNGs are transparent, but the RGB underneath the transparent pixels is dark slateâââinvisible if a renderer honors the alpha channel, an ugly charcoal box if it doesnât. And whether a renderer honors alpha turned out to depend entirely on where the book got opened.

On Kindle, my first fix baked the calloutâs background color into each PNG and dropped the alpha outright. It looked perfect on a light-mode Paperwhite and terrible on Kindle for iOS, which does honor transparency and doesnât paint the callout tint behind the imageâââso every emoji got a colored rectangle instead of a dark one. Iâd traded one box for another. What shipped keeps the alpha intact but rewrites the RGB of every fully transparent pixel to white: renderers that honor alpha get clean transparency; the ones that flatten it get white, which vanishes on a white page.

The print PDF needed the opposite fix. Ghostscript converts the paperback to PDF/X-1a with -dNOTRANSPARENCYââârequired to keep the text as selectable vectors instead of a 300-DPI bitmapâââand that flag always drops the alpha. So there, transparency is the enemy: each emoji is flattened onto the exact color it sits on (the calloutâs gray, or white for body text) with no alpha at all, so thereâs nothing to drop and no box to reveal. The same bug needed opposite fixesâââone runtime might drop the alpha, the other was guaranteed to. (share this quote) A Python script bakes both sets at build time.

Itâs the most ordinary bug in the worldâââmine just happened to be in a book.14

With the tree transformed, each format renders down its own pathâââand the paperback takes the longest road. HTML and the PDF both render through Pandoc, but the PDF is drawn by WeasyPrint, an HTML/CSS-to-PDF engineâââso I typeset the entire book with the same box model Iâd use for a web page. The EPUB goes a different way: Pandoc embeds WOFF2 font subsets, then postprocess-epub.js repackages it to stay valid and small. And the paperback keeps going after everyone else has stopped, out to Ghostscript to become PDF/X-1a:2001âââCMYK color, an embedded USWebCoatedSWOP ICC profile, flattened transparencyâââthe archaic print standard IngramSpark (the print-on-demand distributor) wonât live without.

That same 576-page gate earns its keep here too. check-pdf-page-count.js pins the count and fails the build the moment the paperback drifts off 576 pagesâââcheap insurance against a stale page count shipping as a wrongly-sized cover spine.

Thatâs five publishable files out of one Markdown source15âââwhich raises the obvious question: how do I know any of them are actually correct?

The prose linters keep the words honest; a larger slice of that Vitest suite keeps the build honest. A Pandoc upgrade or a stray line of CSS can silently break a format, and I wouldnât find out until a readerâs Kindle rendered the wrong font. The tests check the machinery: that each transform does what it claims, andâââthe biggest cluster by farâââthat typography hasnât quietly drifted across small caps, table borders, print contrast, dark mode, and the e-ink emoji fallback. Each is a test because each is a way Iâve watched a build look fine and render wrong.

But unit tests only prove my code is right, not that the output is validâââso each artifact also runs through the same validators the stores themselves use, and a rejection happens on my laptop, not on upload day. Every EPUB goes through EPUBCheck, the validator every store runs on upload, and Ace by DAISY, an accessibility audit built specifically for ebooks; the built HTML gets crawled by lychee for link rot and run through an HTML validator before malformed markup can become a malformed ebook.

None of this is something I have to remember to run. Every push to main fans out into 14 parallel CI jobsâââbuild the CSS, then the EPUB, Kindle EPUB, print PDF, PDF/X-1a, HTML, and DOCX side by side, and validate each as its artifact comes ready. A green check means every format built and passed every gate; a red X means somethingâs off before Iâve even alt-tabbed away.

And because every build stamps its commit onto the title page, the book is versioned like software. Iâm on release 1.0.1âââwith a tag and a changelog, not a graveyard of final_v3_revised_ACTUALLY_FINAL.docx files. A typo fix is a point release.

Hereâs the irony: after automating everything up to this point, the actual publishing is almost entirely click-ops. Thereâs no git push to production. Each storeâââAmazonâs KDP, IngramSpark for bookstores and libraries, Draft2Digital for Apple Books and Koboâââwants you to log into a web dashboard, upload the EPUB and the print PDF by hand, and re-enter the same metadata (title, description, BISAC categories, keywords, price) into a slightly different form each time. My pipeline builds a flawless artifact, and then I upload it like itâs 2009. (share this quote)

A few things kept even that part honest:

  • Iâm my own publisher.I formed an LLC and bought my own ISBNsâââone per formatâââso the catalogs list me as the publisher, not a free platform ISBN with the retailerâs name on itâââand none of the vendor lock-in that rides along with one, since a free ISBN only publishes through the platform that handed it out.
  • Wide, not exclusive.I skipped Amazonâs KDP Select, which pays a bit more in exchange for locking the book to Amazon, so the book could be available everywhere at once.
  • The metadata is versioned too.The description, categories, and keywords live in the repoâââa single source of truth I can diffâââand an ONIX feed is generated from it for the channels that accept one. I still paste it into web forms by hand, but at least Iâm pasting from a file under version control.

The nice thing about a book that builds like software: the same machinery keeps paying off after launch.

  • Translations.Because the content is structured Markdown, translating it is closer to localizing an app than retyping a manuscript. Iâve got a pipeline (Brazilian Portuguese, Latin American Spanish, German) that drafts a translation of the finished English book, pins every heading ID so cross-references donât break, enforces a glossary so key terms stay consistent, and back-translates the result to check it against the originalâââthe same trust-but-verify instinct as the prose linters, just across languages.- 16
  • An audiobook.If I record one, turning 100,000 words into narration opens a whole new class of bug: mispronunciationsâââso the audiobook has its own QA suite. It transcribes the generated audio back to text and diffs that against the script, checks phonemes on the tricky words, and audits pronunciationsâââall wired into their own CI workflows. Itâs a snapshot test, aimed at my own voice.- 17
  • Releasing the tools.Almost none of this is specific to my bookâââthe validators, the Lua filters, the whole pipeline. Iâd like to clean them up and put them out as open source (a noun, no hyphen), so the next person doesnât have to build it from scratch. And none of it would exist without the open source projects it already stands onâââthe whole book is other peopleâs generosity, compiled.- 18

One Markdown source, a linter with opinions, and a build that yells when Iâm wrongâââhereâs what all that over-engineering added up to.

| Metric | Count |
|---|---|
| Words | ~100,000 across ~70 chapters and sections |
| Print length | 576 pages (6âÃ9â) |
| Published formats | 5 (plus a DOCX I donât ship) |
| Tests | ~70 files, 2,204 cases, ~5,500 automated checks |
| CI jobs per push | 14, in parallel |
| Style rules | ~300 covering 500+ banned terms, over a 5,000-rule grammar engine |
| Commits | 5,000+ to get here |
| Books | 1 (frankly over-engineered, but fun) |

The tooling outgrew the book, too. I pointed the whole apparatus at this blogâs fifteen-year archive and caught a 2011 post that had been saying âits clearâ where it meant âitâs clearâ the entire timeâââthe exact missing apostrophe my linter now flags on every keystroke. Thousands of people read it; nobody ever mentioned it. You donât find out. You have to go looking. (share this quote)

And it wasnât only the tooling that transferredâââthe habits did. The instincts I sharpened hereâââtreat a mistake caught once as a rule that catches it forever, keep a human in the loop on anything a machine drafts, make the build the source of truthâââhave already found their way into how I run my other projects, this site included.19

Iâm not going to tell you to write 5,500 tests for your novel. For most books, most of this is wildly disproportionateâââand thatâs the point. I did it because the marginal cost of one more check, one more format, one more validator was a few minutes and a little curiosity, and because doing it the developer way meant I could write from an iPad away from my desk and trust that main was always shippable. If you spend your days shipping software, the tools you already know will take you further than youâd expect. The publishing industry starts with a word processor. I started with a Git repository (share this quote)âââand Iâd start there again.

Out now

Open&Async

The collaborative software development playbook for remote and distributed teams. Drawing on a decade at GitHub â on Kindle, Apple Books, Kobo & more.

Buy it â $9.99What would you over-engineer, if you pointed your own tools at it?

  • A colleague called me out on âopen-sourceâ in an early draft; I vowed never to make the mistake again, then made a linter swear to it for me. â©
  • Chief among them: I lost a weekend to LaTeX, on the theory that the tool serious typesetters swear by had to be worth learning. It isâââif you typeset for a living. For me it was a wall of arcane macros and inscrutable errors to approximate a layout CSS gave me in an afternoon. I bailed and never looked back, which is how the whole book ended up drawn with a web box model instead. â©
  • Iâm a terrible speller so I also ran CSpell in VS Code, which shared a custom dictionary with Harper. â©
  • Self-hosted, so it ran on-device tooâââwell, in the Codespace. â©
  • The roster, grouped. Typography:validateSmallcapsandvalidateEscapedSmallcaps(acronyms rendered in small caps, not accidentally escaped),validateEmDashes,validateNumberRangeDashes(en-dash ranges),validateQuotePunctuation,validateListPunctuation,validateBoldKeywords.Structure:validateHeadingHierarchy(no skipped levels),validateDuplicateHeadings,validateParagraphLength(no walls of text),validateMinOpenerLength,validateSectionIntros/validateSectionIntroHasLead/validateSectionIntroFinalLead/validateSectionIntroNoTldr(section intros lead into the content without restating the TL;DR),validateTldrLength,validateChapter,validateIndexIntegrity(index.ymlmatches the files on disk).Links & references:validateCrossReferences,validateNoBareUrlLinkText,validateFootnotes,validateAcronymExpansion(expanded on first use).Callouts:validateCalloutBalance(managers paired with ICs),validateCalloutDivs,validateRoleCalloutLabels.Voice & anti-AI:validateGitHubTense,validateOpenSourceHyphenation,validateHypotheticalHooks,validateSentenceStarters,validateDoubledWords.Housekeeping:validateNoTodoMarkers,validateNoTransitionTodos,validateBuildIdentifiers,validateSSML(audiobook markup). â©
  • jscpd is not prose-specific. Itâs a great way to DRY up code. â©
  • The other twelve, briefly: Consistency (drift in the bookâs own conventions), Taglines (bumper-sticker lines worth pulling into callouts), Proofread (typos and mechanical slips), Cross-reference (broken âsee chapter Xâ pointers), Acronyms (unexpanded on first use), Jargon (the âexplain it to a new hireâ test), Alt-text (missing or non-descriptive), Evidence (assertions with no why or how), Structure (heading hierarchy), Readability (sentences hard to parse on one read), Inclusive (non-inclusive language, judged in context), and Emphasis (overused bold, italics, and scare quotes). â©
  • audit-chapter-lengths.js/audit-paragraph-lengths.jsflag outlier chapters and walls of text;reading-time.jsclocks each chapter and the whole book at 238 wpm (average adult non-fiction speed);check-epub-size.jsenforces the size budget;consistency-check.jssettles âopen sourceâ vs âopen-sourceâ and âasyncâ vs âasynchronousâ book-wide; andwriting-dashboard.jsrenders it all to HTML. Plus a handful of advisory local checkersâââproselint, GNUdiction, and GNUstyleâââfor wordiness and readability stats. â©
  • Not just a metaphor. Kindleâs modern format (KF8/AZW3) is rendered by a WebKit-based engineâââthe same rendering lineage as Safariâââdown to Amazonâs own -webkit-CSS extensions; a Kindle book really is a little offline web page. But the engine ages slowly across a long tail of e-ink models still in readersâ hands, so the HTML and CSS it reliably supports lag years behind the modern webâââhence âvery stripped-down.â â©
  • AI could have generated something competent, and that was exactly the problemâââcompetent and generic is the failure mode I spent 5,500 checks scrubbing out of the text. The cover is the one thing a reader judges before reading a word, so it was the last place I wanted a machineâs default taste. A human designer got the brief; the algorithm didnât. â©
  • Locking the length isnât fussinessâââthe whole physical book is built around that number. A page count sets the bookâs thickness, so the spine width, and the cover art wrapped around it, is computed from it. Drift off 576 pages and the cover no longer fits the spine: fixing it means a round trip back to the human designer to re-render the wrap, not a one-line code change. More on that gate below. â©
  • The rest, in the same spirit: the copyright page is present and bottom-aligned on screen; H2 is larger than body text and H3 smaller than H2; links are underlined, code blocks have a dark background and inline code a visible one, and blockquotes carry a left border; and body text uses the Tailwind Typography proseclass. â©
  • update-revision.jsstamps the build SHA and version onto the title page, so every copy traces to its commit;links-to-footnotes.jsdoes the print link-to-footnote rewrite; and a per-format manifest handles the reshuffle. Same source, subtly different books. â©
  • I spent years at GitHub, where emoji are practically load-bearingâââreactions, :shipit:, an emoji picker in every comment boxâââand they turned up a fresh encoding edge case like clockwork. Emoji, uh, find a way. â©
  • The build actually emits a sixth artifactâââa DOCXâââwhich I used to gather early feedback from reviewers in Word, with track changes and comments. Itâs a working format, not something I publish. â©
  • The pipeline drafts and self-checks, but it doesnât get the final word. If I decide to actually publish any of these, a human native speaker will review and proof the translation firstâââmachine-assisted, human-approved. â©
  • The QA suite catches drift, but a transcript diff canât hear a flat line reading or a subtly wrong emphasis. If I release it, Iâll listen to the finished narration end to endâââevery word ear-checked by a human before it ships. â©
  • The full list, with thanks to their maintainers: Tailwind CSS and Tailwind Typography, Pandoc, WeasyPrint, Ghostscript, qpdf, Poppler, Source Serif 4, Source Sans 3, and Source Code Pro by Adobe, Twemoji, ImageMagick, sharp, SVGO, PostCSS and cssnano, Remark and Retext (and countless ecosystem plugins), textlint, markdownlint, Prettier, Harper, Vitest, Playwright and axe-core, Ace by DAISY, EPUBCheck, htmlproofer, lychee, html-validate, Pillow, LanguageTool, Vale, proselint, GNU Diction, yamllint, js-yaml, jscpd, and just. â©
  • Case in point: my résumé is a print-only web page the build renders straight to PDFâââa different engine than the book (headless Chrome here, WeasyPrint there), but the same âan ebook is a website in a trench coatâ idea, pointed at paper. â©