17: Never Lose Work - transcript
Jessica: The QUILL Cast, episode seventeen. I'm Jessica. Today is the last episode of the everyday-editor arc, and the topic is the one that lets every other topic matter: never losing work.
Liam: I'm Liam. Quick recap of episode sixteen, because today's safety stack sits on top of it. Episode sixteen was languages and thesaurus: the per-tab Document Language, the global Spell Check Language, the offline thesaurus built on the MyThes data file, and the AI thesaurus in AI, More, AI Thesaurus. The key distinction was that the spell-check dictionary and the document language are two different settings that happen to share a word. Today's distinction is similar in shape: we have several safety features, and the words snapshot, backup, and version are not synonyms, even though they all describe ways of keeping your text.
Jessica: That drift has been in the older scripts. The original episode seventeen used snapshot to mean three different things, and the code uses three different words. Today's episode is going to use the words the code uses, because the listeners who follow along with the menu or the keymap editor will care about the difference. The mental model is layered, second by second, minute by minute, save by save, milestone by milestone, project by project, disaster by disaster. Each layer catches what the previous one can't reach.
Liam: And the goal of the episode is not to make you an expert in the safety stack. The goal is to make you trust it. Trust is the prerequisite for the next arc. Episode eighteen opens documents and formats, and it is going to assume you are willing to break things, restructure boldly, and try the wild reorganization. The safety stack is the foundation under that assumption.
Liam: Layer one is undo, the layer you have probably never thought about, because everything else is layered on top of it. QUILL's undo history is per-tab, in-memory, and unlimited in practice within a session. The shortcut is control Z, the redo is control Y or control shift Z. The undo stack stores enough state to rebuild any prior buffer state, which means a typo, a deletion, a paste mistake, a formatting accident, all of it is one keystroke away from being un-done. The undo stack is local to a tab. Close the tab and undo goes with it, which is why close is a verb we will come back to.
Jessica: The first do this now beat. This one is fast, so pause the audio if you need a few seconds.
Liam: Do this now. Open any document, type a sentence you do not mean to keep, hit control Z until it is gone, then control Z one more time so you have undone the undo, and you are looking at your sentence again. That last step matters. It tells you control Z is not destructive. It is a position in a history. You can move forward and backward along that history freely. Resume the audio when you are done.
Jessica: Welcome back. The reason we started with undo is that it is the layer you have already been using. The reason we put it on the chart is that it is the only layer that operates entirely inside a single tab, in memory, with no disk involvement. Every other layer writes to a file somewhere, and once a file is written, your mental model of what undo can recover changes. Undo protects you within a session, within a tab. Nothing else does.
Liam: Layer two is autosave and crash recovery, the heart of the episode. The code is in quill core autosave and quill core recovery, and the contract is small and honest. While you work, on an interval that defaults to thirty seconds and is configurable in settings, the current editor text is written to a hidden snapshot file in the QUILL data directory. The path is app data dir slash autosave slash session id, where the session id is a fresh UUID generated when QUILL launches. Every snapshot is a single dot snap file named with a SHA-1 of the document path, an ISO-style timestamp, and a counter suffix to break ties when two saves land in the same microsecond.
Jessica: The honest detail: the snapshot is a full copy of the buffer text, not a diff, and the snapshot folder is bounded. The autosave module keeps the most recent ten snapshots per document, removing older ones on every save. So you have ten checkpoints, not an infinite history. The snapshots live in your QUILL data directory, which on Windows is percent appdata percent slash QUILL, and they are tied to that session id. If QUILL exits cleanly, the recovery state file records clean exit true, and on the next launch nothing is offered. If QUILL exits abnormally, the next launch finds clean exit false and the previous session id, and the recovery flow begins.
Liam: The recovery flow. On the next launch, after the data directory is created, begin session is called with the new session id. That function loads the recovery state, finds the previous session id and the clean-exit flag, and if the previous session was not clean, it locates the latest non-empty snapshot in app data dir slash autosave slash previous session id. If there is one and you have not already dismissed that specific snapshot, a RecoveryOffer is built. The offer carries the session id, the snapshot path, the cursor position saved at the last autosave, and a count of how many times you have already dismissed the offer for this snapshot. That is what the offer dialog shows.
Jessica: The dialog itself is built by show crash recovery dialog in main frame, the function the dialog-inventory gate attributes as the crash-recovery modal. The dialog is 780 by 520 pixels: an intro line explaining Quill detected an unclean exit, a Snapshot preview read-only RichEdit control, named Snapshot preview, showing the first thirty lines, a Logs folder field showing the path to your logs, and six buttons. Restore Latest Snapshot is the default and the focused button. Open Logs Folder reveals your logs in the file manager. Clear Logs wipes them. Save Diagnostics builds a redacted diagnostic bundle, more on that in a moment. Send Bug Report files a GitHub issue. Skip Recovery dismisses the offer. The dialog uses apply modal ids, the standard keyboard contract gate, so escape, enter, and tab all behave the way you expect.
Liam: The accessibility detail in this dialog is non-obvious and worth saying out loud. The preview control is a wx TextCtrl with TE rich two, the RichEdit style, not a plain read-only edit. A plain ES read-only edit does not expose its value through UIA or IA2 on Windows, which means NVDA and JAWS would announce the field but read no content. The RichEdit style fixes that, and the preview announces as actual text.
Jessica: The dismissal behavior is a real behavior, not a cosmetic one. The code tracks a per-session dismissal count. After three dismissals of the same offer, the intro text changes: You have dismissed this recovery offer three times. Press Restore to keep the recovered version, or Skip to discard it and continue with a blank document. And the Skip button label changes from Skip Recovery to Discard and Continue. The recovery offer is not nagging. It is a count of how many times you have been offered this specific snapshot. A different snapshot, from a different crash, starts the count over.
Liam: Layer three is backups on save. The code is in quill core backups, and the function is backup document. The contract is one line: every save, before the file is written, the previous on-disk state of the file is copied into the QUILL data directory under app data dir slash backups slash doc key, where doc key is a SHA-1 of the resolved document path. Each backup is a single dot bak file named with an ISO-style UTC timestamp and a microsecond counter. The file is opened with the document's own encoding, the previous text is written to it, and the path is returned. The previous file is on disk before the new file is.
Jessica: The detail to flag, and this is the kind of detail that matters when you actually need the backup: backups are only created on a real save of a file that already had a path. An untitled document with no path has no backup history, because there is no previous on-disk state to copy. Backups are not the same as autosave snapshots, even though they look similar. Backups are saved on the save command, with the user as the trigger. Autosave is on a timer, in the background, with no user trigger. Backups live in backups, autosave lives in autosave, the file extensions are different, the folder names are different, and the retention policy is different.
Liam: The retention policy is, for the record, the loosest of the three. Backups are not pruned by the code in the way restore points are. If you want to control backup retention, that is a deliberate configuration choice, not a silent cap. We will come back to retention when we get to the restore points layer, where the real retention logic lives.
Jessica: Layer four is what the menus call Restore Previous Version, and the code calls restore points. The file is quill core restore points, and the design is the most sophisticated of the bunch. The user-facing command is registered in main frame restore points as file dot restore previous version, no default key, assignable in the Keymap Editor. When you invoke it, the dialog shows a list of earlier versions of the current document, formatted as Today at 4:12 PM, 2,341 words, with a source tag that says before a restore when the version exists because you previously restored an even earlier one.
Liam: The storage layout, from the docstring, is restore points slash doc key slash index dot json for the entry list, and restore points slash doc key slash blobs slash SHA-256 dot txt for the content. The list is content-addressed per document, so saving unchanged text costs nothing: the record restore point function hashes the text, compares the hash to the most recent entry, and skips if they match. The blob is only written when the hash is new, and the blob write is itself a write-temp-then-replace dance, the same atomic pattern we will see in the storage layer. Index updates go through write json atomic.
Jessica: The retention policy is where restore points earn their keep. The newest five are always kept, regardless of age. Everything from the last seven days is kept. From seven to thirty days, one version per day. Beyond thirty days, one version per week. On top of the age thin, there is a size cap, default two hundred megabytes per document, configurable in settings as restore points max MB, and the cap only prunes survivors, not the newest five, so a size cap can bound your disk usage but can never erase your recent history. Pruning is called automatically from the save hook, so you do not have to think about it.
Liam: The restore itself is designed to be safe. The dialog has three buttons: Restore, Open as Copy, and Close. Open as Copy is the non-destructive choice. It opens the chosen version as a new untitled tab, leaving the current document untouched. Restore is the destructive choice, but it is destructive with a guardrail: before swapping the editor text, the function calls record restore point with source restore, which snapshots the current text first. So restoring a version is itself recorded as a restore point. The label says before a restore, so when you look at the history later, you can see exactly which versions came from restores. The original code and the new code, both recoverable.
Jessica: The other safety detail: the newest snapshot is filtered out of the dialog when it matches the current editor text. The candidate filter is read restore point, and if the candidate's text equals the editor's current value, it is skipped. Showing the just-saved state as a previous version would be confusing, and the code knows that. The dialog's intro line tells you how many earlier versions you have, with a real plural, one earlier version, two earlier versions.
Liam: Layer five is workspace snapshots, and this is the one that the older scripts called something else. The file is quill core sessions, and the UI lives in main frame sessions. The user-facing command is File, Save Snapshot, with a default filename of the current document name plus dot quill-session dot json. The companion command is File, Open Snapshot, and the File menu also has a Recent Snapshots submenu. The terminology in the menus is Snapshot, and the code uses session, but the meaning is the same: a saved file that records the set of currently open documents, the active tab, and the per-tab caret positions.
Jessica: What the snapshot captures is your desk. Every tab that is currently open, the cursor position in each tab, which tab is active, and the order. When you open the snapshot, the open documents are restored in that order, the active tab is selected, and the cursors land where they were. There is a saved-quill-session-json file you can copy, email, commit, or back up. The session payload is JSON, and the caret positions are collected fresh at save time, not stale from the last autosave.
Liam: The interesting guardrail in the open path: if the saved active index is out of bounds for the current document list, which can happen if you hand-edited the JSON and removed a document, the open path announces to the screen reader that the saved active document is unavailable, and what document it is opening instead. The active index is clamped to a valid value, but the announcement tells you the clamp happened, so a focus shift to an unexpected tab is not a surprise. That is the same honesty contract we saw in the recovery dialog.
Jessica: One more thing about snapshots. They are the layer the older episode seventeen conflated with autosave. They are not the same thing. Autosave is automatic, per-document, in the QUILL data directory, tied to a session id, and gone after a clean exit. Snapshots are manual, per-workspace, in a file you choose, and they live as long as the file lives. You can have five snapshots from last year and ten autosaves from yesterday, and they do not interact.
Liam: Layer six is the write mechanics underneath everything else, the part you will never see and that is the point. The file is quill core storage, and the function is write json atomic. The pattern is the standard one: open a temporary file in the same directory as the destination, with a UUID-suffixed name so concurrent writers cannot collide, write the content, flush, fsync, then atomically replace the destination. The temp file is unlinked if anything goes wrong. The replace itself goes through retry on transient lock, which handles the Windows case where an antivirus scanner, a backup agent, or a screen reader's file hook is briefly holding the destination open.
Jessica: The retry policy is in the code: up to five attempts, fifty milliseconds apart, retrying on PermissionError and on a small set of transient errnos that mean sharing violation or lock violation. The function is shared by single-file saves and the directory-tree moves in core data location, because both can hit a momentary lock. The contract: a save either succeeds completely, with the new file in place and the old one gone, or it fails without ever touching the destination. A crash mid-save leaves you the old intact file, never a half-written hybrid.
Liam: The same atomic pattern is used for restore points, recovery state, settings, keymap, and feature flags. The versioned store layer in quill core versioned store wraps that pattern with a migration contract: every user-state file follows defaults in code, disk stores only the user's delta from those defaults, and a file that predates the current shape is backed up and rewritten to the canonical shape once. The migration backup lives in quill core migration backup and is reused for all stores. That is the persistence backbone of the whole product, and it is one of the few pieces of code that every other piece trusts.
Jessica: And there is a defensive detail worth naming, because the older episode was a little vague on it. The read json function returns the default value when a file is missing, and it also returns the default value when a file is present but malformed, bad JSON, bad encoding, I/O error. The corruption is logged. Callers that hold important user config, settings and keymap, additionally quarantine the bad file before resetting, via backup corrupt file in migration backup, so the user's original is always recoverable. A bad config file cannot crash a load path or startup. A bad config file is moved aside, the load succeeds, and the next save writes a clean one.
Liam: Layer seven is the human layer: honest error reporting. When a save fails, the code path is in main frame save file as. The write call is wrapped in a try, and any OSError, which is the umbrella for disk full, permission denied, network drive gone, file locked, is caught and surfaced as a message box with the exact error string and the target filename. The error is not swallowed. The error is not logged silently. The error is shown to you, in a modal dialog with the icon set to error and the OK button, and the message includes both the filename and the underlying OSError text.
Jessica: The contract is that you always know when something failed. The most dangerous data loss is the one you do not know happened. QUILL's contract is the opposite. A save that cannot complete is a save that tells you why. If you ever see a Could not save dialog, read the message, it will tell you what to fix, full disk, wrong path, file in use by another program, network share disconnected.
Liam: And there is a related path for QUILL's own failures. The crash report module is in quill stability, and the user-facing command is Help, Save Diagnostics. The function save diagnostics bundle builds a redacted zip file, default name quill-diagnostic-bundle followed by a UTC timestamp and dot zip, in app data dir slash diagnostics. The bundle contains a metadata dot json with your QUILL version, Python version, platform, safe-mode flag, wx version, feature flags, enabled plugins, and a redacted list of your recent commands. The redaction is the load-bearing part: every text file is run through redact text for bundle with stats, which scrubs secrets, API keys, tokens, and personally identifying information, and the redaction stats are recorded in metadata dot json so you can see what was redacted.
Jessica: There is no document content in the bundle. That is the H-2 rule from the pre-release review. The bundle is for telling the maintainers what went wrong, not for sharing your text. The recent-commands list is filtered to well-formed command ids only, so a malformed entry is dropped silently. The bundle is yours to share or not, and the same is true of Help, Report a Bug, which uses the same redaction and the same consent gate.
Liam: The Help menu also has Open Logs Folder, which reveals the logs directory in your file manager, and View Startup Logs, which opens the most recent startup log directly. The recovery dialog has its own Send Bug Report button, which packages the same redacted bundle, attaches a crash context, and offers to file a GitHub issue. With a stored GitHub token the issue is created directly, and the recovery offer is marked dismissed, so you do not see it again. Without a token, the bundle is saved locally and you are shown the path.
Jessica: Let's assemble the timeline, because layered is the whole trick and the listeners will want one picture to hold in their head. Second by second: undo, control Z, in memory, per tab, gone when the tab closes. Minute by minute: autosave, every thirty seconds by default, ten snapshots per document, gone when the session ends cleanly. Save by save: backups, one dot bak per save, in the backups folder, kept as long as the disk keeps them. Milestone by milestone: restore points, content-addressed, age-thinned, size-capped, listed in File, Restore Previous Version. Project by project: workspace snapshots, in a file you choose, capturing the whole desk. Disaster by disaster: recovery, offered on the next launch. Underneath it all: atomic writes, with retry on transient Windows locks. Above it all: honest error reporting, and a redacted diagnostic path.
Liam: The behavioral payoff, and this is the real point of the episode, is that the safety stack changes how you write. Writers who fear loss save compulsively, restructure timidly, and avoid experiments. Writers who trust the net cut a version before a big edit, take a snapshot before a project switch, and try the wild reorganization because undo is one keystroke away and restore points are a menu away. The safety stack is not infrastructure. It is courage.
Jessica: And one correction versus the older episode seventeen, because the brief told us to flag drift. The older script described snapshots as document versions and versions as workspace snapshots, and it used the word snapshot for three different things. The code is clear: restore points are document versions, workspace snapshots are saved sessions, and backups are the pre-save copies in the backups folder. The three words are not interchangeable, and if you only remember one thing from today's terminology, remember that. The recovery dialog calls itself Crash Recovery. The version list lives under File, Restore Previous Version. The workspace list lives under File, Save Snapshot.
Liam: Another drift to flag, and this is the kind of detail the listeners who follow along with the Help menu will care about. The older episode mentioned a Save Diagnostics item under the recovery dialog and a separate Report a Bug under Help. Both still exist, and the redacted-bundle path is the same. The new detail in the current build is that the recovery dialog also has Clear Logs and Open Logs Folder, which is the hands-on way to look at your own logs without ever building a bundle. If something is misbehaving, Open Logs Folder, read the most recent log, and you will usually see the error.
Jessica: One more honest correction, this one about backups. The original episode implied that backups were pruned automatically, and the current code does not prune them the way restore points are pruned. Backups accumulate in the backups folder until you remove them, by hand or by a script. If you want bounded disk usage for backups, that is a deliberate configuration choice, not a default. The default is keep everything, because the cost of an unkept backup is invisible until the day you need it.
Liam: Workflow recipe. Before any big edit, two minutes of housekeeping. Step one, save the current document. Saving creates a backup and a new restore point in one motion. Step two, if the edit is structural, a whole-file reorganization, a chapter move, a docstring refactor, take a workspace snapshot first. File, Save Snapshot, give it a name that includes the date and the project, click Save. Step three, make the edit. Step four, if the edit goes sideways, File, Restore Previous Version, pick a restore point, choose Open as Copy to look at the old version in a new tab, or Restore to swap the editor text.
Jessica: For long-running projects, the cadence is the same shape. Monday, save snapshot grant-application, save the file. Tuesday through Friday, work as normal, autosave every thirty seconds, backups on every save, restore points accumulating. Friday, save snapshot grant-application-end-of-week. The snapshots become a ledger, one per week. Combined with restore points, you have two independent time machines: one for the desk and one for the document.
Liam: Accessibility note, because we promised these. The restore points dialog is a wx ListBox with a screen-reader-friendly name, Earlier versions, and rows that announce as today at 4 colon 12 PM comma 2 comma 341 words, the same front-loaded format a screen reader speaks naturally. The buttons are Restore, Open as Copy, and Close, all keyboard reachable, with Restore as the default. The workspace snapshot dialogs are wx FileDialog instances, with the standard keyboard contract. The recovery dialog uses RichEdit for its preview, as we covered, which means the snapshot text is actually read. Every flow in the safety stack is keyboard complete. Mouse is never required. If you find a flow that is not, that is a bug, and Help, Report a Bug is the right next step.
Jessica: Homework, four steps, as the brief asked. One: open QUILL, hit control Z on something you just typed, then control Y, and confirm the round trip. That is the undo layer, the layer you already trust. Two: trigger a save on a real document, then File, Restore Previous Version, and confirm there is at least one earlier version listed. That is restore points, the layer you might not have known you had. Three: open two or three tabs that you actually use together, File, Save Snapshot, give it a name, close QUILL, relaunch, File, Open Snapshot, and confirm the desk reassembles. That is workspace snapshots, the layer for project-level state. Four: open Help, find Save Diagnostics, run it, and look at the resulting zip. Do not send it anywhere. Just see what is in it, see that your text is not in it, and see the redaction stats. That is the diagnostic path, the layer for when QUILL itself is the problem.
Liam: That completes the everyday-editor arc. You are now fast, safe, and fluent, and you know exactly what the safety stack is doing on your behalf. Episode eighteen opens the documents-and-formats arc, and it starts with the single most load-bearing layer underneath everything else: Markdown and the structure it carries. The hash is the substrate, and a dozen features consume it. The next episode is going to assume you are willing to break things, and today's stack is the foundation under that assumption.
Jessica: For reference, that is episode seventeen of fifty-four in the series. We are now done with part two, the everyday-editor arc, and the remaining arcs cover documents and formats, speech and audio, AI, the Accessible Vault, Story Studio, GLOW, braille, extensions, and power-user workflows. Exactly on plan.
Liam: Save often, version the milestones, snapshot the desk, and trust the net.
Jessica: I'm Jessica.
Liam: I'm Liam. This has been The QUILL Cast.