Skip to content
Documentation GitHub
Editor

Content Persistence

Content Persistence

Validates that content written in the Inklings editor is durably persisted via the Loro CRDT pipeline and is reliably restored across navigation, app close/reopen, and under high-frequency input conditions. This spec is rated P0 because any regression here constitutes a data-loss event for the user.

Preconditions

  • Vite dev server running on http://localhost:1420
  • A workspace exists and is open

Scenarios

Seed: seed.spec.ts

1. Content persists after navigating away and back

Verify that typed content is saved automatically and survives a navigation round-trip within the same session.

Steps:

  1. Click a new blank page in the sidebar (or create one titled “Persistence Test”) and open it.
  2. Click into the editor body and type “This content must survive navigation.”
  3. Watch the save indicator in the toolbar — wait until it shows “Saved”.
  4. Click on a different page in the sidebar to navigate away.
  5. Click on “Persistence Test” in the sidebar to navigate back.

Expected: The body text “This content must survive navigation.” is still present in the editor after the round-trip. The editor does not show a blank or placeholder state.

2. Content persists across page reload

Verify that content survives a full browser page reload, simulating an app restart.

Steps:

  1. Create a page titled “Reload Test” and type “Survives reload” as the body content.
  2. Wait for the save indicator to show “Saved”.
  3. Reload the browser tab (press F5 or Cmd+R / Ctrl+R, or navigate to the app URL again).
  4. Once the app has fully reloaded, open “Reload Test” from the sidebar.

Expected: “Survives reload” is present in the editor body. The page loads without showing a blank or placeholder state.

3. Content typed in the editor is visible after saving and navigating back

Verify that content auto-saved via the editor pipeline is fully readable when the page is reopened.

Steps:

  1. Create a page titled “Block Content Test”.
  2. Click into the editor body and type “Direct block save”.
  3. Wait for the save indicator to show “Saved”.
  4. Click a different page in the sidebar to navigate away.
  5. Click “Block Content Test” in the sidebar to return to it.

Expected: The text “Direct block save” is visible in the editor body. The page title shows “Block Content Test”.

4. Editing content again shows the latest version after navigating back

Verify that editing a page and saving shows the most recent version, not a stale version, when returning to the page.

Steps:

  1. Create a page titled “Snapshot Freshness”.
  2. Type “Version one” in the editor body and wait for the save indicator to show “Saved”.
  3. Clear the body text (select all, press Delete) and type “Version two”.
  4. Wait for the save indicator to show “Saved” again.
  5. Navigate to a different page in the sidebar, then navigate back to “Snapshot Freshness”.

Expected: The editor body shows “Version two”, not “Version one”. The older version is not restored on navigation.

5. Large content persists without truncation

Verify that content near the practical size limit is stored and restored without truncation or corruption.

Steps:

  1. Create a page titled “Large Content Test”.
  2. Click into the editor body and paste or type a large block of text — at minimum 10,000 characters (for example, repeat a sentence many times).
  3. Watch the save indicator — wait until it shows “Saved”.
  4. Navigate away to another page, then navigate back to “Large Content Test”.

Expected: All content is present after the round-trip. The word count visible in the toolbar or page detail panel reflects the full content. No text is silently dropped or truncated.

6. Rapid typing is debounced and the final state is saved

Verify that typing quickly does not cause a partial save to overwrite the final state.

Steps:

  1. Create a page titled “Debounce Test”.
  2. Click into the editor body and type a burst of 50 characters in rapid succession.
  3. While typing, observe that the save indicator shows “Unsaved” (or a pending state).
  4. Stop typing and wait approximately 500 ms for the debounce to complete and the save to finish.
  5. Confirm the save indicator shows “Saved”.
  6. Navigate away and back.

Expected: After navigating back, all 50 characters are present in the editor body. The save indicator shows “Saved”. No characters are missing and no console errors are visible.

7. Content persists with rich formatting

Verify that formatted content (headings, bold, lists) survives the persistence round-trip without losing marks.

Steps:

  1. Create a page titled “Formatted Persistence”.
  2. In the editor body, add an H2 heading by typing ## Overview at the start of a line and pressing Enter.
  3. Type a word and apply bold to it using Cmd+B (or Ctrl+B).
  4. On a new line, create a bullet list with items “Alpha” and “Beta” using the toolbar or - prefix.
  5. Wait for the save indicator to show “Saved”.
  6. Navigate to a different page, then navigate back to “Formatted Persistence”.

Expected: The H2 heading “Overview”, the bold word, and the bullet list items “Alpha” and “Beta” are all present after the round-trip with their formatting intact. No marks are stripped or garbled.

8. Multiple pages each maintain independent content

Verify that saving content to one page does not overwrite or corrupt content on another page.

Steps:

  1. Open (or create) a page titled “Page Alpha” and type “Content of Alpha” in the body. Wait for “Saved”.
  2. Click on a new page in the sidebar titled “Page Beta” and type “Content of Beta”. Wait for “Saved”.
  3. Click on “Page Alpha” in the sidebar to navigate back to it.

Expected: “Page Alpha” shows “Content of Alpha” — the content from “Page Beta” has not been merged into or overwritten it.

9. Save indicator reflects the dirty/saving/saved lifecycle

Verify that the save indicator accurately reflects state changes as the user types.

Steps:

  1. Open a page that was previously saved (the save indicator should initially show “Saved”).
  2. Observe the save indicator — it should show a “Saved” or checkmark state.
  3. Click into the editor body and type a single character.
  4. Observe the save indicator immediately after typing (before the debounce fires).
  5. Stop typing and wait approximately 500 ms.
  6. Observe the save indicator after the save completes.

Expected:

  • Before typing: the save indicator shows a “Saved” or checkmark state.
  • Immediately after typing: the save indicator shows “Unsaved” or a pending/dirty state.
  • After the debounce and save complete: the save indicator returns to “Saved”.

10. Undo works correctly after navigating away and back

Verify that the undo history persists across navigation so that undo operates correctly when returning to a page.

Steps:

  1. Open a page (or create one) and type “Original text” in the editor body.
  2. Wait for the save indicator to show “Saved”.
  3. Click a different page in the sidebar to navigate away.
  4. Click back to the original page.
  5. Press Cmd+Z (macOS) or Ctrl+Z (Windows/Linux) to undo.

Expected: The undo operation reverses the last edit made before navigation — it removes “Original text” or reverts the content to its prior state. The editor does not reset to a blank or error state when undo is pressed after a navigation round-trip.

Test Data

KeyValueNotes
nav_page_titlePersistence TestScenario 1 page
nav_page_contentThis content must survive navigation.Scenario 1 body text
reload_page_titleReload TestScenario 2 page
reload_page_bodySurvives reloadScenario 2 body text
large_content_size10000Minimum character count for scenario 5
debounce_ms300CONTENT_EXPORT_DEBOUNCE_MS in editor
snapshot_pageSnapshot FreshnessScenario 4 page

Notes

  • Auto-save is debounced at 300 ms (CONTENT_EXPORT_DEBOUNCE_MS in InklingsEditor.tsx). Test steps that wait for save should allow at least 500 ms after the last keystroke.
  • The Loro CRDT snapshot is stored as a binary blob in blocks.content_loro. The materialized markdown is stored in blocks.content and pages.raw_markdown. Both are written by SaveBlockContentUseCase.
  • LoroDoc undo is provided by LoroUndoPlugin (not TipTap’s built-in). TipTap’s undoRedo is explicitly disabled in StarterKit.configure({ undoRedo: false }).

Was this page helpful?