Taylor McNeil

docs-as-portfolio v1.5

PUT/aampersand/peering-into-lethe

Peering into Lethe

Context

This is the design companion to Red Thread, Isle Eight . That piece is about finding your way in a labyrinth. This one is about how to lay thread.

Standing on the Banks

I was seventeen chapters in when I forgot I had killed a man.

Not a rando. A named character with a backstory and a family and a reason to be mourned. I had cut him from a scene in Chapter 4, fully intending to move his death somewhere more impactful. Seventeen chapters later, he was still alive in my book, still showing up in scenes, and I had just forgotten that I killed him.

In fact, he has died three times in this book already. Reincarnation is not a thing in this book.

The Lethe is the mythological river of forgetting. In Greek myth, the dead drink from it to erase their memories before being reborn. For a writer, the drafting process works the same way. You generate a massive volume of brilliant ideas — intricate lies, red herrings, character arcs, murder weapons that need to appear and reappear.

But as the prose changes, as scenes are cut and chapters are rewritten, those foundational ideas start to slip into the river. They get lost in scattered notebooks, a sea of sticky notes, or just the fog of your own brain.

Conceptually, aampersand is designed to stand on the banks of that river, grab those ideas, and absolutely refuse to let them float away.

But to do that, I had to rethink what a story actually is.

I had to stop trusting the prose.

The Annotation Trap

If you ask traditional writing software what a story is, they will point to the text. Their mental model is: "The document is the truth." Look at how tools like Microsoft Word or research managers like Zotero handle memory:

ToolAnnotation ModelWhat happens when you delete the text?
Microsoft WordEmbedded HighlightThe comment/annotation is destroyed.
ZoteroEmbedded ReferenceThe citation breaks or vanishes entirely.
aampersandIndependent Database RecordThe Beat becomes a "Ghost."

In Word or Google Docs, an annotation is just a metadata wrapper around a specific string of text. It's fire-and-forget. If you highlight a sentence and leave a comment, that comment lives and dies with the prose. Delete the paragraph, and the annotation goes to the grave with it.

I initially explored injecting beat information the same way — embedding it directly into the document's markup so it could be easily exported. But I realized this doesn't really solve the problem.

If you are writing a dense murder mystery, you are constructing a web of killers and motives. If you delete the murder weapon — say, a knife — from Chapter 7, Scene 1, you need to remember to put that knife back somewhere else.

If the software embeds your plot notes inside the text, deleting the text deletes your note about the knife. The software is actively helping you forget.

What is a Beat?

The core thesis of aampersand is: "The story is the truth. The document is just one expression of it."

Because of that, I couldn't build annotations. We had to build Beats.

A Beat isn't a comment. It's a claim about your narrative. It is a database record that says: "Right here, the reader learns that Misses Puff could not be the killer," or "Right here, the love interest has dug himself into a hole that will be revealed in 3 chapters."

The Anatomy of a Beat

Database: The Story
BEAT_ID: 8f92a
Plotline: "The Theft"
Scene: 12
Status: Healthy
Anchors to
Editor: The Document

The headmaster noted the missing ingredients upon entering the classroom. He immediately suspected his newest pupil.

The text in the editor isn't the beat itself; it's the evidence of the beat. The Investigation Boards, the chapter beat list, the timeline — all of these rely on independent database records, not the prose.

But this created a massive problem. This is writing software. Authors are going to rewrite, hack, slash, and burn their prose. What happens when the evidence changes, but the claim remains?

Ghosts in the Machine

A writer juggling 7 plotlines across 100 chapters cannot manually audit whether every beat still points at the right text. That cognitive overhead is exactly what aampersand exists to eliminate.

So, instead of making assumptions, the system tracks the volatility of the text. When a scene is saved, aampersand examines every beat anchored to it and asks a simple question: Is the mark still there?

The beat mark in the HTML <span data-beat-id="..."> is the anchor. If the mark exists, the beat is tethered to the prose. If it doesn't, the tether is severed. Two states emerge from this:

Stale Beats

The mark exists, but the text inside it drifted. The system compares what you originally wrote against what's there now. If they diverge, it flags this with a gentle sticky note — "Hey, you changed this language. Just checking if the plot point still lands." Critically, it preserves what you first said so it can keep measuring how far the prose has moved.

Ghost Beats

The mark is gone. The paragraph was deleted entirely. The anchor is severed. When a beat becomes a Ghost, the system catches it before it falls into the Lethe. It saves the metadata, strips the document location, and surfaces it in a dedicated Ghost Tray.

The Ghost Lifecycle
┌──────────┐    text edited    ┌──────────┐   text deleted    ┌──────────┐
│ HEALTHY  │ ────────────────► │  STALE   │ ────────────────► │  GHOST   │
│          │                   │          │                   │          │
│ Beat is  │                   │ Beat is  │                   │ Beat is  │
│ anchored │ ◄──────────────── │ anchored │                   │ detached │
│ & text   │   text restored   │ but text │                   │ from all │
│ matches  │   (or re-anchor)  │ drifted  │                   │ prose    │
└──────────┘                   └──────────┘                   └──────────┘
                                                                 │
                                                  ┌──────────────┴──────────────┐
                                                  │                             │
                                                  ▼                             ▼
                                           ┌──────────────┐              ┌──────────────┐
                                           │ RE-ANCHORED  │              │   DELETED    │
                                           │              │              │              │
                                           │ Writer drags │              │ Writer says  │
                                           │ ghost to new │              │ "I don't     │
                                           │  paragraph   │              │  need this"  │
                                           └──────┬───────┘              └──────────────┘
                                                  │
                                                  ▼
                                            ┌──────────┐
                                            │ HEALTHY  │
                                            │   (new   │
                                            │ location)│
                                            └──────────┘

The Ghost state is a fork, not a dead end. The system is saying: "You once said this meant something. Now it's gone. You decide: Put it back somewhere else, or officially kill it."

It's about revealing the state of the story, not managing it for you.

The Undo Paradox

Building a system that remembers what the writer deleted sounds great in theory. Under the hood, it was a nightmare.

Beats exist in two places: our text editor (TipTap) holds the highlighted markup, and our database (Dexie) holds the structural claim.

The Split-Brain Problem
┌─────────────────────────────────┐     ┌─────────────────────────────────┐
│       TIPTAP (The Editor)       │     │      DEXIE (The Database)       │
│ ─────────────────────────────── │     │ ─────────────────────────────── │
│ Has an Undo Stack (Cmd+Z)       │ VS  │ No Undo Stack                   │
│ Rolls back text changes         │     │ Eagerly saves permanent states  │
└─────────────────────────────────┘     └─────────────────────────────────┘

When a user deletes a paragraph, the beat becomes a Ghost. If they drag that Ghost from the tray and re-anchor it to a new paragraph, the database updates permanently.

But what if the user suddenly hits Cmd+Z?

The text editor rolls back. The original deleted paragraph reappears with its old highlight. But the database has already permanently moved the beat to the new location. The two systems split. The editor is lying to the database, and the database is lying to the writer.

I spent weeks trying to fix this. I was writing incredibly complex sync logic — snapshotting previous anchors, running deduplication passes, scanning cross-scene HTML — just to force the database to respect the text editor's undo stack.

To be honest, I just had to ask the AI. We traced through the logs and ran endless loops of what-ifs, how-coulds, and perhaps… ultimately, to land on: actually… would this even happen?

A Single Line of Code

I was designing for a system that was abnormal.

We took a step back and looked at actual human behavior. There is no realistic scenario where someone intentionally re-anchors a ghost beat and then needs to Cmd+Z through the re-anchor, all the way back to the original text deletion.

Accidental keystrokes happen in seconds. Cmd+Z fixes them instantly. But re-anchoring a ghost beat is a deliberate editorial decision. It often happens hours, days, or weeks after the original text was cut. It's like renaming a file on your computer — you wouldn't expect Cmd+Z in a text editor to undo a file rename.

The Siren's Song

"Every action in a text editor must be undoable."

The Truth

"Structural story decisions are permanent until the writer decides otherwise."

The breakthrough came down to one line of code:

applyBeatMark(editor, beatId, from, to, false)

We simply passed addToHistory: false when applying beat marks from structural actions — re-anchoring ghosts, deduplicating marks, cleaning up orphans. The text editor is told: this isn't a drafting action, don't put it on the undo stack. The database write stays eager and permanent.

There is no drift because there is nothing to drift from. TipTap and Dexie always agree. By letting go of the need to "undo" deliberate editorial choices, we solved the problem we'd been chasing.

What the River Washes Back

But the Lethe has a longer memory than we do.

addToHistory: false solved the problem of what we choose to remember. There's a second problem: what undo remembers without our permission.

Consider: a writer re-anchors a ghost beat from Chapter 3 to Chapter 5. The beat now lives in Chapter 5. The database knows this. The re-anchor mark wasn't added to the undo stack. That part is clean.

But the writer's undo stack in Chapter 3 still holds a snapshot from before any of this happened — the deleted paragraph, frozen in time, with the original beat mark baked into its HTML. If the writer goes back to Chapter 3 and hits Cmd+Z enough times, the editor will restore that old paragraph exactly as it was. The writer isn't trying to undo the re-anchor. They don't even know it's happening. They're just restoring a paragraph they cut. And the old mark comes back as collateral — a ghost of a ghost, resurrected by undo's long memory.

Now there are two marks in two chapters for the same beat. The database says Chapter 5. The editor in Chapter 3 says otherwise.

So every time a scene is saved, &mpersand runs a cleanup pass. It scans the saved HTML for beat marks that don't belong to that scene anymore — orphans washed back up by undo — and quietly strips them out. If duplicates surface within the same scene, it keeps the one that matches the database and removes the rest.

You can't solve forgetting once. The river keeps washing things back. You have to keep refusing.

The Shape of Remembering

The ghost state isn't just an editor feature. It's a design principle.

When you haven't pulled a chapter into a pane yet, it's a ghost pane — a placeholder that remembers the slot exists even before you fill it. When you close a support panel, the app remembers what was open. The pattern is the same everywhere: The document changes constantly, the story shouldn't.

Every feature in aampersand eventually has to answer the same question: What happens when the writer changes their mind? The answer is never "lose the data." The answer is always "surface it, and let them decide."

A Siren's Song was about the interface getting out of the writer's way. This was about the database refusing to let the writer forget.

Your document will change a thousand times before you publish. Your story shouldn't have to.

I ignored the Siren's song. I pulled my notes from the Lethe. But a story that remembers itself isn't enough — it needs to see its own shape. To take all those surviving beats, ghosts, and threads and show the writer what they've actually built.

Next month, we will locate an astrolabe.
aampersand

aampersand isn't just a text editor. It's a refusal to let the Lethe wash away your ideas.

Read: Red Thread, Aisle Eight