Skip to content

v0.8.0 — Reading Partner on your phone

Latest

Choose a tag to compare

@github-actions github-actions released this 30 Jul 00:38

Reading Partner runs on a phone. It is not the desktop app squeezed narrow — it is a second shell that carries today's briefing, the articles you kept, and a chat about either, and carries nothing else. No reader, no PDF, no marking: those stay where a large screen and a stylus are. Navigation is a stack you swipe out of from the left edge, and pulling down on the briefing or an article opens its chat. Underneath, two things that only mattered once a second device existed got fixed: a phone no longer downloads a library it cannot open, and sync now runs the moment the app comes back instead of on a timer that a locked phone stops. Sync also stopped picking a winner when two devices edited the same file — it merges them, record by record. And every AI turn is now sized against the model's context window before it is sent, so a long book answers or says why it cannot, rather than failing as if the network were down.

New

  • The phone shell. Below the sm breakpoint with a coarse pointer the app mounts a different shell: a launch screen with today's briefing above the articles you have kept, the briefing and article screens, the kept list, and the chat. It holds no reading state and never loads the PDF engine, so the phone bundle is a fraction of the desktop one. The form factor is decided from the screen's shorter side and the pointer type, never from the operating system, and ?shell=phone forces it for development on a desktop.
  • Back, three ways, one definition. The phone keeps a navigation stack; back is a pop. Swiping in from the left edge follows your finger and commits past a third of the screen, the Android system back button is the same action, and a chat that is open closes before anything navigates.
  • Pull down to ask. On the briefing or an article, pulling down from the top grows a band with a labelled pill — "Ask about today's briefing", "Ask about this article" — that turns solid past the threshold. Release and the chat for that content opens. It is not pull-to-refresh: nothing regenerates, and speed alone never commits, because a fast downward flick is what arriving at the top looks like.
  • Keep an article under a topic. The star on a briefing card files the article into a topic, stripped of inlined images and sanitised, with its publication date and whether only a summary was available. The topic page lists what you kept and opens it for reading. Identity is the article's URL, so keeping it again updates rather than duplicates.
  • A briefing run survives being interrupted. Collection now checkpoints after every source, so a run cut off by a phone going to sleep, or by the system reclaiming the app, resumes on the next launch and fetches only the sources still outstanding — the expensive half is never repeated. Only the same day's run resumes; anything older is discarded. The screen is held awake while a run is in flight.
  • Two devices that edited the same file both keep their edits. Sync merges instead of choosing: records are matched by id and merged individually, prose files line by line, settings field by field, against the last state both sides agreed on. What a merge drops is kept locally for thirty days.
  • Every AI turn is measured against the model's context window before it is sent, and reduced to fit — history first, then the material. A turn that still will not fit says so in a sentence about the reading rather than a sentence about tokens, and a run that spends its whole round budget looking things up says that instead of failing silently.

Changed

  • Sync runs on the app's lifecycle, not only on a clock. Coming back to the front runs a full pass immediately; leaving pushes whatever is not yet uploaded. The five-minute timer remains as a backstop, but it never ran on a phone in your pocket.
  • A phone does not mirror the book library. The books channel is off there — no download, no upload — while library.json still syncs, so the phone knows what exists without storing hundreds of megabytes it has no way to open.
  • Change detection is by content, not by clock. A file that was rewritten with the same bytes is unchanged, so a device that merely re-saved no longer wins the file.
  • Each file on Drive carries its own revision in its metadata, and the remote state comes from one directory listing. manifest.json is no longer maintained, only read once to migrate older devices.
  • A book uploads in 1 MB chunks instead of one request for the whole file, so a slow or lossy connection makes progress rather than starting over.
  • Only models with a 1M-token context window are offered.
  • The trace list drops the star; a row is deleted by swiping it.
  • Reaching for a colour reaches for the pen: picking a colour selects the marking tool with it.
  • Past days' derived briefing files are deleted when a new briefing generates. One article cache measured 4.3 MB; conversations are untouched.
  • The microphone appears only where something can record. Voice input is a desktop feature and stays one — a phone keyboard already dictates, and better.

Fixes

  • A phone launched in landscape is still a phone. The shell is chosen once at mount, and measuring width alone put a phone held sideways into the desktop shell for the rest of the session.
  • Paged mode saves the page you are looking at, not the one at the edge of the viewport, and opening a book in paged mode lands on one whole page. Switching to vertical keeps the page you were on, and a rotation settles the way a layout switch does.
  • Opening a book never flashes page 1 on the way to where you were.
  • A book that will not open says so instead of showing an empty reader.
  • A finished stroke no longer selects the mark it just drew, and switching tools no longer opens the annotation editor.
  • The colour palette leaves the band that scrolls the tool rack, so scrolling the rack no longer drags the palette with it.
  • One unreadable or rejected file no longer stops the whole sync pass; a cached Drive id that has gone missing heals instead of poisoning every later pass; a manifest that failed to download no longer reads as an empty remote and wipes local data; a device that has never synced is told that rather than shown an invented date; a stopped sync reads as stopped rather than as broken.
  • A token refresh cannot hang forever, and the watchdog stops retrying failures that cannot improve — a refusal is answered once, not four times.
  • A tap that lands on a button it cannot focus no longer cancels a pending delete.

Internal

  • App.tsx gave up four more responsibilities: lesson prep, book notes, the settings cards, and the info call's React-free half. The phone shell reuses the info screens rather than copying them; the briefing card is one component both launch screens mount.
  • The gestures are state machines in plain TypeScript with unit tests, apart from the DOM: the edge-back machine, the pull-to-ask machine, the navigation stack, the shell choice, the call layout.
  • Two measured surprises are written down. A window that merely loses focus fires no visibilitychange, so "in front of the user" has to be tracked as a state across four events. And the threshold at which a browser hands a touch to a scroll container is not a distance you can tune — it withholds the first move until its own slop is passed, so a gesture must claim the very first move it is given.
  • The pitfall index is grouped by subject with a lookup table at the top, so a change to one area scans one group instead of the whole list. Seven entries whose code no longer exists were deleted and the engine-migration leftovers marked historical.
  • Handwriting was removed from the roadmap; the strategic decision against ink annotation was already on the record and the roadmap entry contradicted it.