Haute Lumière · Nº — · Standing notes

The Shelf

Six pieces of glass, three hundred and thirty-five volumes, and one number that has to stop being two numbers.

What is actually on disk at ~/Developer/HauteLumiereReader/app as of this morning, what the live house is answering, and the seven things that would make it the best reading application anybody has shipped. Read against a clean macOS build and a live call to the Reader, not against memory.

335Titles
on the shelf
275Openable
right now
139Narrations
in the bucket
11.08mWords
across six shelves
0Of them
can be listened to

Before anything else


The house is selling the same shelf at two prices.

This is not a design note. It is the one thing on this page that costs money while it stands, and it is getting wider by the hour — the store was built against one decision and the app is being built against another.

QuestionThe Shelf & the Readerstore.hautelumiere.life
The membership$22.22 a month$44.44 a month
What is free firstNinety daysSeven days
A single volume$7.99 in the payload, no longer offered in the appNot offered
How many volumes335283
Where it is written/api/shelf, Till.swiftthe-one.mjs, build.mjs, live Stripe link

A third file, house-prices.mjs, holds yet another arrangement — $44.44 for a book and $22.22 for the month. And Till.swift opens with a comment saying Apple's introductory offer is set to three months "so the app and the web agree." They do not agree. A reader who joins on the web and then opens the app is paying twice what the app quotes them, and the first thing they will do is ask for the difference back.

One price, in one file, read by the store, the Worker and the app. Everything else on this page is an improvement. This one is a leak.

The smallest version that ships

Move the decision into the-one.json, have the Reader Worker serve it at /api/offer, have build.mjs read it instead of its own HOUSE const, and have the app take the trial length from Apple rather than from a sentence in a masthead. Then the number is written once and everything downstream is a consequence.

Seven movements


What would make it the best one anybody has shipped.

Ranked by what each returns against what it costs. Every one of these is half-built already — the models exist, the endpoints answer, the entitlements are declared. Nothing here is a new idea; it is finishing the ideas that are on disk.

I

The narration is the whole reason to have an app, and there is no player.

A hundred and thirty-nine books are read aloud and sitting in R2. Title.audio is modelled, listening computes the length, UIBackgroundModes: audio is already declared in project.yml — and no file in the app imports AVFoundation. A browser cannot play with the screen off, in a car, or on a wrist. This is the one thing the app can do that the web cannot, and it is the one thing it does not do.

Ship

A single line under the chapter head — Listen · forty-one minutes — an AVPlayer, and MPNowPlayingInfoCenter so the lock screen and CarPlay carry the book's own title. Then the flourish nobody asks for and everybody notices: the ear and the eye share one bookmark. Press listen and it starts at the paragraph you were reading; stop listening and the page is open where the voice left off. Same /api/place row, one extra field.

II

Nobody can sign in.

Reader.signIn(token:) is written and nothing in the app calls it. So me.signedIn is false forever, which means Carrying never has a row, the room never opens, a membership bought on the web never arrives, and the five gigabytes are unreachable. It is a single missing screen holding four features shut.

Ship

Sign in with Apple, and a plain field for the house key underneath it. This is the cheapest unlock on the page and it is a prerequisite for three of the six below.

III

The search is built, answering, and unused.

/api/search?q=…&deep=1 returns chapter-level hits with a where line for each. Reader.search() wraps it. No view calls it. The field on the shelf runs a contains against the titles already on screen — so eleven million words are searchable and what the reader gets is a filter on a list they can already see.

Ship

Point the existing field at the endpoint, and let a hit open to the paragraph rather than to the book. One field, eleven million words, landing you inside a sentence. This is the highest-value hour in the project.

IV

The room is advertised and the room is a stub.

The hard half is done: hautelumiere-signal is deployed, a Durable Object per room, and fourteen hundred lines of real end-to-end encryption sit in Correspondence beside the view. The Warden and the Covenant are written. And RoomView calls none of it — it appends to a local array and simulates delivery with a four-hundred-millisecond sleep. The on-device check is genuinely good work and it is currently guarding a mock.

Ship

Replace the local array with SignalAPI and the sleep with the stream — the endpoint is already answering. This is the smallest gap on the page between what is built and what a reader can touch. Until it closes, the app's own TitleView is the standard to hold it to: a shelf that hides what is missing cannot be trusted about what is there. The same applies to what it claims it has.

V

Five gigabytes are sold twice on one screen and shown nowhere.

The masthead and the colophon both promise them. Me.Storage.says is written, tested against the right units, and renders "1.2 of 5 GB" — a state rather than a gauge with a red end, exactly right — and no view ever calls it. There is also nowhere in the app to put a file.

Ship

Either give it a room — a reader's own margin: their notes, their marked passages, their own manuscripts on the same shelf as the house's — or drop the promise from both places. A margin is the better answer: it turns a storage number into the reason somebody stays.

VI

Three hundred and thirty-five books with no covers — and that is the opportunity.

The instinct is to commission 335 covers. The house already has a better move and it is cheaper: each shelf carries its own register, and a register is not a colour, it is a grade. The Press reads by candlelight, the Hundred at dusk, Economics through the long afternoon.

Ship

One plate at each shelf head, bled, taken from the Luminous set and graded to that shelf's register — and the register travels with you into the book, so opening a volume from the Hundred visibly lowers the light. Six photographs instead of three hundred and thirty-five covers, and a shelf identified by its hour rather than by a wall of thumbnails.

VII

The watch and the television are declared and empty.

Apps/Watch and Apps/TV contain no files; both targets compile the shared tree and branch on surface. That is the right architecture and the wrong content — a shelf of 335 rows is not a thing a wrist can hold.

Ship

The watch gets exactly one screen: the narration you are inside, and a crown that scrubs it. The television gets exactly one screen: the same, at ten feet, with the chapter title set large enough to read from a sofa. Two files each, and both depend on movement I.

Two smaller things, worth more than they cost


The best sentence in the house is buried in a masthead.

"The first chapter of every book is free to anybody, forever, with no account and no card. That is not a trial. It does not expire." That is the whole marketing strategy and it is in paragraph three. This morning's edit already moved it onto the row — every locked title now reads Chapter one on its right-hand side instead of a price, which is the correct call. Finish it: make that row shareable. A link out of the app that opens chapter one of that exact book on the web, for somebody with no account, is the cheapest acquisition channel the house will ever build, and it is a share sheet.

Keep the verifying sentence. Put it on the web too.

The app tells a buyer: "The sources in this volume are still being checked. It is sold anyway, and said so here, because you should know what you are buying." Nothing on store.hautelumiere.life says that, and a hundred volumes of the Luminous Hundred are in exactly that state. It is the most trustworthy sentence in the whole estate. It should not be the app's alone.

The order


If it were mine for a week.

  1. One price file. Nothing else matters while two storefronts disagree by a factor of two.
  2. Sign in with Apple. The single missing screen that unlocks four features.
  3. The player. The reason the app exists; the entitlement is already declared.
  4. Search, pointed at the endpoint that already answers.
  5. The room, wired or unsold. One or the other, not the current third thing.
  6. Six plates, six registers. The shelf stops being a list.
  7. The wrist and the room. One screen each, both riding on the player.

Three traps


The accent in the product name

A PRODUCT_NAME carrying an accent will not codesign, and the error points at the main executable while reading "code object is not signed at all." It is why the product is Shelf and the display name is The Shelf. Already paid for once. The comment at the top of project.yml is what keeps it paid for.

The narration that was a string

Narration was once modelled as a bare string, and that alone made the entire shelf refuse to decode — a blank page, not a degraded one. The live payload still sends audio: null for most titles. Keep it optional, and keep the comment saying why.

The shelf that arrives without its books

/api/shelf sends shelves and totals and no flat list; Catalogue.all is computed from the shelves, which is correct today and will silently return nothing the day the Worker starts paginating. Worth an assertion rather than a discovery.