Release update

0.4.1 0.5.0

Most of the work never needed to happen.

Opusghost is a portable writing studio. It lives on a USB stick, opens a folder you already own, and writes on real A4 pages — your manuscript stays a plain file on your own drive. This release is mostly a performance one: a hundred and four commits, and a pass that deleted more work than it added.

Every figure below is reproducible from the repository, and the measurements that did not work are still recorded there — just not advertised.

88%

less JavaScript before you type a word

Default editor startup, analytics and recording off. The same source, two packagings — not two historical releases.

3,813,453 B → 453,849 B

0.5.0 in four numbers

What landed since 0.4.1.

Counted straight from the history: everything committed after the 0.4.1 hotfix for the Windows black screen. 34d5ba9

104 commits between 0.4.1 and this build
258 files touched across the app, shell and site
+48,534 lines added, against 3,919 removed
−88% startup JavaScript, the headline win below

How it went

Five passes, in the order they landed.

The performance work was not a single inspired afternoon. It was the third of five passes, and the only one that was measuring from the first commit.

Sep 21–22

Phone updates and document history

The phone app could read the same USB library but had no way to update itself, and the desktop kept history only in memory. This pass added a digest-verified over-the-air update path, a thin native helper that owns nothing but durable file operations, and history that survives a restart with a safe restore and a working undo.

expo-updates OTA thin native FS helper two-file durable save safe restore + undo
Sep 23–24

A native shell, and the UI thread handed back

The desktop moved off Electron onto Tauri 2, and Word import and export stopped running where you type — they moved into a background service, so a large document no longer freezes the window while it converts. The page-turn jank that had been there since the beginning was fixed in the same window.

docx import/export off the UI thread Electron → Tauri 2 page-turn jank fixed native menus, open-with, quick note
Sep 28–29

The performance sweep

Seventy-two commits in two days, most of them tests. The renderer was taken apart and put back together around one rule: do not ask the browser to measure the same thing twice. Redundant re-measures, repeated style reads, a quadratic buffer trim and a cache miss on every repeated letter all went in here — along with the durability work that made a save atomic rather than hopeful.

linear recording trim redundant re-measures removed line-height cache ink pen-path cache atomic saves loopback + per-request token
Sep 30–Oct 1

Offline packaging and stalled frames

The pass changed how the app is built rather than how it runs: real ES modules with lazy boundaries instead of one bundled file, which is where the 88% comes from. It also fixed two genuine stalls that could leave the editor locked when a document was opened while the window was hidden.

minified local ES modules optional features on demand suspended-frame recovery Magic Paper curl preserved reproducible release archives
Oct 2–4

A writing desk that got busier, not heavier

The last pass added the most visible features in the release without spending any of the startup budget the previous pass bought: signed plugins installed from inside the app, streaming dictation, offline word prediction, writings promoted to books on a shelf, and the phone editing the same library over an encrypted link.

signed writing plugins streaming dictation offline ghost text writings as books encrypted phone access

The performance work

The three worth reading twice.

Each of these removed work rather than doing the same work faster. The output is identical — that is checked against the old code, not assumed.

1

The app ships a tenth of itself

Opusghost used to bundle everything into one file and load all of it before the first page appeared. Word import and export, ZIP handling, analytics and recording were all in that file, whether you used them today or not.

Production builds are now minified local ES modules with named lazy boundaries, so tree shaking still works and the optional features load on demand. Nothing was moved to a CDN — a stick with no network gets exactly the same app.

Same-source comparison

Not a claim that the offline cache is 453 KB. The site precaches every chunk on first load, so Word and recording work offline without ever being opened while online. The desktop app keeps all of them on disk for the same reason.

Before 3,813,453 B
After 453,849 B

−88% startup JavaScript

Before 3,813,453 B
After 1,813,932 B

−52% all JavaScript, every optional chunk included

Before 770,883 B
After 549,046 B

−29% gzipped — a compression estimate, not captured traffic

2

Turning a page stopped copying the book

To draw one page of the live manuscript as a turning sheet, Opusghost deep-cloned the entire document and then deleted every block that was not on that page. On a 5,000-paragraph manuscript that was 20,002 elements created and destroyed per page turn, to keep 34 of them.

The clone now skips the descendants it was only going to delete. The resulting snapshot is the same page. Sheet shells, which cloned the whole editor just to empty it, went from 4,005 elements to 3.

Real macOS WebKit

Clone timings in the actual system webview: medians over nine alternating before-and-after trials, 256 clones each. These are not end-to-end frame-rate claims. Animation keyframes, easing, strip counts, durations and typography are untouched.

Before 20,002 el
After 34 el

−99.8% elements cloned per turn, 5,000 paragraphs

Before 2.797 ms
After 0.215 ms

13× clone time, 5,000 paragraphs

Before 4,005 el
After 3 el

−99.9% sheet-shell clone, 1,000 paragraphs

3

The recorder stopped re-serialising to save memory

To stay under its 2 MB cap, the recorder dropped one event at a time and re-serialised the whole buffer with JSON.stringify to check whether it still fitted. That is quadratic, on the renderer’s main thread, every 500 events — while you are typing.

It now finds the range to discard in a single pass and copies the survivors once. The live recorder sizes each event as it arrives and evicts incrementally, so the trim no longer runs on the keystroke path at all.

Recorded baseline

The first three buffer sizes in the original table were all 1 ms. The cost only appeared once the buffer crossed the cap, which is exactly why it survived so long. Output was verified identical by running the old implementation beside the new one over 21,000 randomised cases: zero mismatches, zero mutated inputs.

On these meters the before column is the recorded baseline from the work that fixed the bug, which cannot be re-measured without reverting the fix. The after column was re-measured on the current build, so small differences against the originally recorded figure are run-to-run variance. Both are batch-trim or append-plus-snapshot timings on synthetic events — not typing latency, and not process-RAM measurements.

Before 2,061 ms
After 8 ms

257× trim of a 12,000-event, 2.2 MB session

Before 88.58 ms
After 5.87 ms

15× batch compaction, 24,000 events

Before 17.14 ms
After 3.75 ms

4.6× batch compaction, 12,000 events

The rest of the work

Smaller, but the same story.

Redundant re-measures removed, a style read hoisted out of a loop, identical output cached by construction. The last column says how much each number is worth, because they are not all worth the same.

Performance improvements in 0.5.0, their before and after values, and the basis of each measurement
Change Before After Gain Basis
Re-measures per 200-page read-through 200 0 eliminated call count
Layout reads per avoided re-measure, 500 blocks ~1,950 0 eliminated call count
Ink typing, 66 characters at 78×78 1,420 ms 585 ms 2.4× recorded
Computed line-height reads, 1,000 blocks 1,633 one per parent 4 reads → 1 call count
Cloned elements, 1,000-paragraph page turn 4,002 34 −99.2% exact count
Clone time, 1,000-paragraph page turn 0.520 ms 0.055 ms 9.5× real WebKit
Page-position lookup in a 1,051-page book 1,051 probes — left linear measured, optimal
Pagination across 50 → 800 blocks 1.0× 16.5× clean linear, no cliff call count

The last two rows are the ones that did not change, and they are on the page deliberately. Pagination already scales linearly with no cliff, block measuring is already a single batched read pass, and all three view modes already share one array of page positions. A binary search over page positions runs in 0.83 µs, once per pagination — it would be complexity for nothing, so it is not there.

Also new in 0.5.0

The rest of the release.

The performance pass was the third of five. Here is what the other four were doing — because most of the release is also the reason the performance work was possible.

New since the performance pass

Writing plugins you install from the app

Dictation, autocorrect and autocomplete are now signed packages with their own settings, installed, updated, rolled back and uninstalled without ever touching a file by hand — and without Node, Bun or Python on your computer.

Dictation that streams, and can be paused

Moonshine Small Streaming in English and German: recognition starts while you are still talking, punctuation arrives from the model, and spoken commands like comma or neuer Absatz insert it. Stop releases the microphone; Escape cancels the uncommitted speech.

Ghost text that learns your manuscript

Fast and Smart prediction, trained and run entirely offline in Rust — no model download, no service, no Python. Suggestions appear at the end of a block, Tab accepts one as a single undoable edit, and the learned checkpoint is yours and stays local.

Your phone, editing the same library

Leave the stick plugged in and the desktop open: the phone connects directly over peer-to-peer Wi-Fi or Bluetooth and edits that computer’s Texte/ folder, with saves landing in the desktop’s git history. Authenticated AES-256-GCM, no cloud relay, pairing that lasts only for the session.

And the groundwork it all stands on

Writings are books now

An author, a cover you can see before you open it, imports from a folder or a Word file, and shelf actions that rename, move or re-cloth a book without going into its text.

A native shell instead of a bundled browser

The desktop app moved from Electron to Tauri 2, so it uses the operating system’s own webview instead of shipping a second one. Your Texte/ library and data/ folder are untouched; upgrading from an Electron install takes one full download.

Word import and export left the UI thread

Converting a large .docx used to happen where you type, which froze the window. It now runs in a background service, and a routine save resolves as soon as the file is durable instead of waiting for history to commit.

History that survives a restart

Every library keeps real version history, written with git, with a safe restore and an undo that works. On the phone app the version ring is off by default and per document.

It behaves like a native app

A real application menu, files that import when you drop them on the window or open them with Opusghost, native notifications when an export finishes, and a quick-capture hotkey that is registered for the session and nothing else.

Saves are atomic, and the edges are closed

A document save writes to a temporary file and renames, so an unplugged stick cannot leave a half-written chapter. Documents are size-capped, the local helper binds to loopback only and requires a per-process token, and the release archives are byte-for-byte reproducible.

And the app this all sits in

Word files, automatically

JSON is the source document. Every save also writes the sibling .docx, so Word and Pages can open the same folder you wrote in — and a plain .docx dropped in becomes a writing again.

Folders, search, an outline

Nest manuscripts however you like, find one by name, and jump straight to a heading from the chapter outline.

Focus, and two typing styles

Hide the library and the toolbars. Turn on a gliding caret, or let each letter draw itself like handwriting.

Export and print

Word, PDF, Markdown or a plain web page — and a real system print dialog that uses the printer you already have, in the typeface you wrote in.

Method

Three kinds of number. They are not interchangeable.

Most published performance work blurs these together. Keeping them apart is the reason any of it is worth reading.

Exact counts

Layout reads, splitter calls, cloned elements. These are integers the code produces itself, so they are identical on every machine and every run. When a number here moved, it moved because the code asked for less.

Most trustworthy

Real WebKit timings

The page-clone figures come from the actual macOS system webview: medians over nine alternating before-and-after trials, with the old algorithm frozen and running side by side. Not a browser stand-in, and not a proxy metric.

Machine-specific, shape is real

Synthetic timings

Recording and pagination use generated inputs chosen by the harness, not captured from a real session. The shape of these curves is the finding; the absolute milliseconds belong to whichever machine ran them.

Directional only

Where a number is not measured, this page does not pretend it is. Native binary size, whole-download size and total process RAM reductions are not measured and are not claimed here — they may well be real, and you should ask for them before believing them. Neither is the ~1,950 layout reads an end-to-end timing; it is a count of a primitive we no longer ask for. Gzip figures are a reproducible compression estimate, not captured network transfer.

Reproduce all of it: bun run bench bun run bench:pages bun test scripts/*.test.ts 34d5ba9..HEAD

Getting it

Three steps, then you are writing.

The site installs the app for the computer you are on. You bring the drive; nothing is uploaded.

Plug in a stick

Any 4 GB USB drive will do; 8 GB or more if the library is going to grow. Format it exFAT and Mac, Windows and Linux can all read it.

Let the site install it

Choose Set up a USB stick and pick the drive. Opusghost, your Texte/ folder and a note for next time are copied across. No admin rights, nothing uploaded.

Write. Unplug. Keep writing.

Double-click Opusghost.app on the Mac, run the .bat on Windows or Linux. Carry the stick to any computer and the library comes with you.

No account, because there is nothing for one to hold. Nothing is uploaded — not the text, not the titles, not the timings. Unplug mid-sentence and the file on disk is still the last good one. No drive access in your browser? You get plain zip downloads instead: unzip them with the built-in tools and run.

You should feel the speed,
not read about it.

Open a long document, turn a few pages, switch views, start dictating. The work above is the reason none of that makes you wait.