Skip to content
Documentation GitHub
Workspace

Workspace Page CRUD

Workspace Page CRUD

Covers the complete create-read-update-delete lifecycle for pages within a workspace, including validation edge cases at the system boundary. Getting these right is foundational — every other feature (hierarchy, trash, search, sync) builds on page persistence.

Preconditions

  • HTTP bridge running on port 9990
  • A workspace initialized via initialize_workspace before each scenario
  • Bridge shim injected via playwright.config.ts

Scenarios

Seed: seed.spec.ts

1. Create a page with a valid title

The simplest create case: a top-level page with a plain ASCII title.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Type “My First Page” as the page title in the title field that appears.
  3. Press Enter or click away to confirm the title.

Expected: The page “My First Page” appears in the sidebar page tree. The editor opens showing the title “My First Page”. The page is accessible by clicking it in the sidebar.

2. Create a page — slug derivation from title

Slugs are derived by lowercasing the title and replacing special characters with hyphens.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Type “The Hero’s Journey!” as the page title.
  3. Confirm the title.

Expected: The page “The Hero’s Journey!” appears in the sidebar. The page is navigable and its content is editable. The underlying slug (visible in any URL or breadcrumb) follows the pattern the-hero-s-journey.

3. Get a page by slug

After creation, the page must be accessible by navigating to it in the sidebar.

Steps:

  1. Create a page titled “Retrievable Page” using the “New Page” button.
  2. Click on “Retrievable Page” in the sidebar to navigate to it.

Expected: The editor displays “Retrievable Page” as the page title. The editor body area is visible with at least one empty block ready for content. Creation and update timestamps are present (visible in any page metadata panel).

4. List all pages in a workspace

All non-deleted pages appear in the sidebar.

Steps:

  1. Click the “New Page” button and create a page titled “Page Alpha”.
  2. Click the “New Page” button again and create a page titled “Page Beta”.
  3. Observe the sidebar page tree.

Expected: Both “Page Alpha” and “Page Beta” are visible in the sidebar. The sidebar shows at least two pages.

5. List page tree returns hierarchical structure

The sidebar tree must reflect parent-child relationships.

Steps:

  1. Create a page titled “Parent” at the root level.
  2. Right-click “Parent” in the sidebar and select “New Subpage” (or click “New Page” while “Parent” is selected and choose to nest it).
  3. Type “Child” as the subpage title.

Expected: The sidebar shows “Parent” with a disclosure triangle (expand arrow). Expanding “Parent” reveals “Child” nested beneath it. The nesting structure is visually correct.

6. Update a page title

The title can be changed after creation.

Steps:

  1. Create a page titled “Original Title” and navigate to it.
  2. Click on the title field in the editor (at the top of the page).
  3. Clear the title and type “Updated Title”.
  4. Click away or press Enter to save.

Expected: The editor heading shows “Updated Title”. The sidebar updates to show “Updated Title” in place of the old name. The change is reflected immediately.

7. Update a page’s text content

Content in the editor body can be typed and saved.

Steps:

  1. Create a page titled “Content Test” and navigate to it.
  2. Click into the editor body area (below the title).
  3. Type “Hello, world!”.
  4. Click away or wait for autosave.

Expected: The text “Hello, world!” is visible in the editor body. Navigating away and returning to the page shows the content is persisted.

8. Update page title and content simultaneously

Both the title and body can be edited in the same editing session.

Steps:

  1. Create a page titled “Both Fields” and navigate to it.
  2. Update the title to “New Both Fields”.
  3. Click into the body and type “Some content here.”.
  4. Save (click away or wait for autosave).

Expected: The page title in the editor shows “New Both Fields”. The body shows “Some content here.”. The sidebar also reflects the new title “New Both Fields”.

9. Update page — no changes (empty update) returns current state

Opening a page and making no edits leaves it unchanged.

Steps:

  1. Create a page titled “Stable Page” and navigate to it.
  2. Click on the title field without changing it.
  3. Click away without typing anything.

Expected: The page title remains “Stable Page”. No error occurs. The page is still accessible from the sidebar.

10. Soft-delete a page (move to trash)

Deleting a page moves it to trash; it must not appear in the sidebar but must be accessible from the trash view.

Steps:

  1. Create a page titled “To Be Deleted”.
  2. Right-click “To Be Deleted” in the sidebar.
  3. Select “Delete” or “Move to Trash” from the context menu.
  4. Confirm the deletion if a confirmation dialog appears.
  5. Observe the sidebar.
  6. Navigate to the Trash view (if available in the sidebar).

Expected: “To Be Deleted” disappears from the sidebar page tree. If a Trash section is accessible, “To Be Deleted” appears there.

11. Soft-delete followed by navigating to the page returns NotFound

After soft-deletion, the page is no longer accessible via normal navigation.

Steps:

  1. Create a page titled “Gone Page”.
  2. Delete it via right-click → Delete in the sidebar.
  3. Try to navigate to it (e.g., if it was open in the editor, observe what happens; or attempt to open it from search).

Expected: The page “Gone Page” is no longer shown in the sidebar. Any attempt to navigate to the deleted page results in an empty state or “Page not found” message in the editor area.

12. Create a page — empty title is rejected

An empty title must not be accepted.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Leave the title field empty (or clear it if pre-filled).
  3. Attempt to confirm by pressing Enter or clicking away.

Expected: The page is not created. Either the title field shows a validation error (“Title cannot be empty”), the input is not committed, or the new page entry disappears from the sidebar without being saved. No untitled page persists.

13. Create a page — whitespace-only title is rejected

A title consisting entirely of spaces must be treated as empty.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Type three spaces into the title field.
  3. Attempt to confirm by pressing Enter.

Expected: The page is not created. A validation message appears or the entry is discarded. No page with a blank or whitespace-only title persists in the sidebar.

14. Create a page — title at maximum length succeeds

The maximum allowed title length is 200 characters.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Paste a title that is exactly 200 characters long (200 “a” characters).
  3. Confirm the title.

Expected: The page is created and appears in the sidebar with the 200-character title (possibly truncated visually in the sidebar, but the full title is shown in the editor heading). No error occurs.

15. Create a page — title exceeding maximum length is rejected

A title of 201 or more characters must be rejected.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Paste a title that is 201 characters long.
  3. Attempt to confirm.

Expected: The page is not created. A validation error appears indicating the title is too long (e.g., “Title must be 200 characters or fewer”). No page with the overly long title appears in the sidebar.

16. Create a page — Unicode title is stored and retrieved correctly

Titles with multi-byte Unicode characters (emoji, CJK, right-to-left text, etc.) must display correctly.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Type or paste “勇者の旅 🗺️” as the title.
  3. Confirm the title and navigate to the page.

Expected: The page appears in the sidebar with the title “勇者の旅 🗺️” (the characters and emoji are not corrupted). The editor heading shows the same title correctly.

17. Create a page — Unicode title at 200-character limit

A title using multi-byte characters must respect the 200 Unicode code point limit (not byte count).

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Paste a title of exactly 200 Japanese characters (200 repetitions of “あ”).
  3. Confirm the title.

Expected: The page is created successfully. The title displays correctly in the editor. This confirms the limit is counted in Unicode code points, not bytes.

18. Create a page with a long title that produces an empty slug

When a title consists entirely of non-ASCII/special characters that are stripped during slugification, the page is still created.

Steps:

  1. Click the “New Page” button in the sidebar.
  2. Type ”!!! @@@” (only punctuation characters) as the title.
  3. Confirm the title.

Expected: The page is created (the title is non-empty and passes validation). The page appears in the sidebar with the title ”!!! @@@”. The page is navigable and editable. Note the exact slug behavior for regression tracking.

19. Get page — invalid slug characters return Validation error

Path-traversal sequences in navigation must be rejected.

Steps:

  1. Attempt to navigate to a page using a path-traversal slug (e.g., by manipulating the URL or any direct slug input field to contain ../etc/passwd).

Expected: No page is loaded. An error state or “Page not found” message is shown. The app does not attempt filesystem access for the invalid slug.

20. Get page — empty slug returns Validation error

An empty slug is invalid and must not be navigated to.

Steps:

  1. Attempt to navigate to a page with an empty slug (e.g., by opening an empty page URL directly in the browser context).

Expected: The app shows an error state or redirects to a safe state. No page content is loaded for an empty slug.

21. Get page — non-existent slug returns NotFound

Navigating to a valid-format slug that has no corresponding page shows a not-found state.

Steps:

  1. Attempt to navigate directly to a page with the slug “this-page-does-not-exist” (e.g., via a direct URL or search).

Expected: The editor area shows a “Page not found” message or equivalent empty state. No server error occurs.

22. Page has exactly one initial block after creation

Every newly created page starts with one empty block ready for editing.

Steps:

  1. Click the “New Page” button and create a page titled “Block Check”.
  2. Navigate to the page.
  3. Observe the editor body.

Expected: The editor body shows exactly one empty block (the cursor lands in it, ready for typing). No extra blocks exist. The block is empty (no placeholder content other than a hint like “Type something…“).

The page detail panel shows augmented metadata for the page.

Steps:

  1. Create a page titled “Detail Test” and navigate to it.
  2. Type “Hello world” in the editor body.
  3. Open the page detail or metadata panel (if available in the UI — e.g., a sidebar panel or info icon).

Expected: The detail panel shows a word count greater than 0 for the body content. Backlinks and outgoing links sections are present (empty for a new page with no cross-links). Timestamps (created, updated) are shown.

24. Rename a page updates slug and persists

Renaming a page via its title field changes both the display name and the underlying identifier.

Steps:

  1. Create a page titled “Before Rename” and navigate to it.
  2. Click on the title “Before Rename” in the editor and change it to “After Rename”.
  3. Save the change.
  4. Observe the sidebar and editor.

Expected: The editor heading shows “After Rename”. The sidebar updates to show “After Rename”. Any navigation or link that used the old title no longer resolves to this page under its old name.

25. Rename page preview does not mutate data

A rename preview or plan can be viewed without committing the change.

Steps:

  1. Create a page titled “Preview Me”.
  2. Begin a rename to “Preview Me — Renamed” but do not confirm it (e.g., start editing the title, view any rename preview dialog, then cancel).
  3. Observe whether the title has changed.

Expected: After cancelling, the page title remains “Preview Me” in the editor and sidebar. The rename preview (if shown) was non-destructive.

26. Set a page icon

A page can have an icon displayed next to its title in the sidebar.

Steps:

  1. Create a page titled “Icon Test” and navigate to it.
  2. Look for an icon picker near the page title (e.g., an emoji or icon button next to the title in the editor header).
  3. Click it and select an icon (e.g., a book icon) and a color (e.g., purple).

Expected: The selected icon appears next to the page title in the editor header and in the sidebar entry for “Icon Test”. The color is applied to the icon as selected.

27. Clear a page icon

A previously set icon can be removed.

Steps:

  1. Create a page titled “Clearable Icon” and set an icon (e.g., a user icon with blue color).
  2. Click the icon picker again and choose the “No icon” or “Remove icon” option.

Expected: The icon is removed from the editor header and sidebar entry. “Clearable Icon” appears in the sidebar with no icon decoration.

28. Set page type to Folder

Pages can be assigned the Folder page type.

Steps:

  1. Create a page titled “My Folder”.
  2. Find the page type selector (e.g., in the page metadata panel or a right-click context menu).
  3. Change the type to “Folder”.

Expected: The page “My Folder” displays a folder icon or “Folder” type label in the sidebar or page header. The type change is reflected visually and persists after navigating away and returning.

29. Multiple pages with similar titles get distinct slugs

Two pages with the same title are both created without conflict.

Steps:

  1. Click “New Page” and create a page titled “My Page”.
  2. Click “New Page” again and create another page also titled “My Page”.
  3. Observe both entries in the sidebar.

Expected: Both pages titled “My Page” appear in the sidebar as separate entries. The second one may have a disambiguation indicator (e.g., “My Page (2)”) or share the visual title while having a distinct underlying slug. Neither creation fails.

30. Save and retrieve block content snapshot (LoroDoc round-trip)

Typed content is persisted and retrievable across navigation.

Steps:

  1. Create a page titled “Loro Test” and navigate to it.
  2. Type “test” in the editor body.
  3. Navigate away to another page.
  4. Navigate back to “Loro Test”.

Expected: The editor body shows “test” as previously typed. The content survived the round-trip through the storage layer and is correctly loaded on re-navigation.

Test Data

KeyValueNotes
valid_titleMy First PageStandard ASCII title
special_char_titleThe Hero’s Journey!Produces slug the-hero-s-journey
unicode_title勇者の旅 🗺️CJK + emoji; title must round-trip exactly
cjk_200_title”あ” * 200200 CJK code points; at length limit
max_title”a” * 200ASCII at maximum 200-char limit
over_max_title”a” * 201One over the limit; must be rejected
empty_title""Must be rejected
whitespace_title” “Must be rejected (three spaces)
punct_only_title”!!! @@@“Valid title, but produces ambiguous slug
traversal_slug../etc/passwdMust be rejected by slug validation
nonexistent_slugthis-page-does-not-existValid format slug that has no corresponding page

Notes

  • The bridge uses a non-CRDT “legacy path” for content updates via update_page. True CRDT content is handled via save_block_content / get_block_content_snapshot. For QA purposes, typing in the editor is sufficient for testing that text persists.
  • rename_page derives a new slug from the new title and updates all wiki-link references in the workspace. In a fresh workspace with no cross-links, the rename plan will have zero affected references.
  • Slug collision handling (scenario 29) is implemented in the SQLite repository; the exact disambiguation suffix (e.g., -2, -3) should be documented here once observed during test execution and stabilized in the test data table.
  • set_page_icon validates icon names and color tokens at the domain level. Invalid icon names (containing spaces or special characters) and invalid color tokens (not in the approved palette) will be rejected. This is not tested in this spec but is covered by domain unit tests.

Was this page helpful?