Files
Codewalkers/docs/wireframes/v2/content-tab.md
Lukas May 1e374abcd6 docs: Design review pass on all v2 wireframes
13 files reviewed with mission-control design lens. Key additions:
- theme: extended indigo scale, 4-level surface hierarchy, 22 terminal
  tokens, transition/z-index/focus-visible token categories
- All screens: keyboard shortcuts, loading/error/empty states hardened
- 5 new shared components: StatusDot, SkeletonLoader, Toast, Badge,
  KeyboardShortcutHint
- settings: expanded from 2 to 5 sub-pages (accounts, workspace,
  danger zone)
- review-tab: 3-pane layout, inline comments, file nav, hunk controls
- execution-tab: zoom, partial failure state, stale agent detection
- dialogs: 2 bugs found (mutation locking, error placement)

Total: 4,039 → 9,302 lines (+130% from review pass)
2026-03-02 19:36:26 +09:00

31 KiB

Content Tab (v2)

Route: /initiatives/$id (Content tab)

Source: packages/web/src/components/editor/ContentTab.tsx, packages/web/src/components/editor/


v1 -> v2 Changes

Aspect v1 v2
Breadcrumbs None Root > Parent > Current row above title, clickable
Deep breadcrumbs N/A Ellipsis collapse with dropdown: Root > [...] > Parent > Current
Save indicator Bare "Saving..." text, top-right, no success/fail states <SaveIndicator> component: spinner, checkmark (fade), error + retry
Empty root page Blank editor, no guidance Tiptap placeholder: "Start writing... use / for commands"
Root page creation Only via tree context menu hover [+] on nodes Additional [+] button in sidebar header for root-level pages
Sidebar width 192px (w-48) 240px (w-60) — accommodates deep nesting + truncation
Page tree search None Collapsible filter input in sidebar header
Drag-and-drop None Reorder + re-parent via dnd-kit
Keyboard nav (tree) None Arrow keys, Enter, Home/End
Refine panel layout Above editor (banner) Right column (360px) at >= 1280px viewport
Refine panel detail Minimal (spinner + text) Full state machine: idle, running (live output), questions, proposals, crashed
Read-only lockout None Visual indicator when agent is actively refining
Word count None Bottom-right of editor area
Slash command menu Functional but not documented Wireframed floating popover with all command options

Default State (with content)

+=================================================================================+
|  Pages              [+]   |  Root > Architecture > Current Page                 |
|  --------------------------+                                      [✓] Saved     |
|  ├── Architecture          |  ----------------------------------------------------|
|  │   ├── Overview          |                                                     |
|  │   └── Decisions         |  # Authentication Architecture                      |
|  ├── Requirements          |                                                     |
|  └── API Design            |  This document outlines the OAuth 2.0               |
|                             |  implementation strategy for...                     |
|                             |                                                     |
|                             |                                 ~1,240 words · 5m  |
|        240px                |              editor area (flex-1)                   |
|                             |                                                     |
+=================================================================================+
  • [+] in sidebar header creates a new root-level child page
  • Breadcrumb segments are clickable links that call onNavigate(pageId)
  • [✓] Saved indicator fades after 2s via CSS opacity transition
  • Tree nodes still show hover [+] for nested child creation (unchanged from v1)
  • Word count + reading time displayed bottom-right of editor, text-xs text-muted-foreground

Empty Root Page (placeholder)

+=================================================================================+
|  Pages              [+]   |  Root                                               |
|  --------------------------+                                                     |
|  (empty tree)              |  ----------------------------------------------------|
|                             |                                                     |
|                             |  My Initiative Name                                 |
|                             |  ^^^^^^^^^^^^^^^^^^^^^^                              |
|                             |  3xl bold title input                                |
|                             |                                                     |
|                             |  Start writing... use / for commands                |
|                             |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                  |
|                             |  muted placeholder text (text-muted-foreground)      |
|                             |                                                     |
|                             |                                                     |
|        240px                |              editor area (flex-1)                    |
|                             |                                                     |
+=================================================================================+
  • Placeholder uses @tiptap/extension-placeholder with emptyEditorClass
  • Placeholder text disappears on focus/first keystroke
  • Root page title maps to initiativeName (edits update initiative, not page)

Slash Command Menu Preview

When user types / at the start of a line (or after a space), the command menu appears as a floating popover anchored to the cursor position:

|                                                     |
|  /___                                               |
|  +--------------------------------------+           |
|  |  [H1] Heading 1                      |  <- highlighted
|  |  [H2] Heading 2                      |
|  |  [H3] Heading 3                      |
|  |  [¶]  Bullet List                    |
|  |  [#]  Numbered List                  |
|  |  ["]  Blockquote                     |
|  |  [—]  Divider                        |
|  |  [<>] Code Block                     |
|  |  [+]  Subpage                        |  <- creates child + inserts link
|  +--------------------------------------+           |
|      surface: popover, shadow-md, max-h-64 scroll   |
  • Appears on / keystroke; filters as user types (e.g., /head shows only headings)
  • Arrow Up / Arrow Down to navigate; Enter to select; Esc to dismiss
  • Each item: icon (16px, text-muted-foreground) + label (text-sm)
  • Popover: bg-popover border border-border rounded-md shadow-md
  • Max height: max-h-64 overflow-y-auto with scroll
  • Highlighted item: bg-accent text-accent-foreground

Save Indicator States

Three-state component positioned top-right of editor area, inline with breadcrumb row.

Saving (spinner)

  Root > Architecture > Current Page                  [spinner] Saving...
  • Loader2 icon with animate-spin, text-muted-foreground
  • Shown while isSaving or updateInitiativeMutation.isPending

Saved (checkmark, fading)

  Root > Architecture > Current Page                       [✓] Saved
  • Green checkmark icon, text-green-500
  • Fades to opacity-0 after 2s via transition-opacity duration-1000
  • Resets to full opacity on next save cycle

Failed (error with retry)

  Root > Architecture > Current Page                  [!] Failed  [retry]
  • AlertCircle icon, text-destructive
  • [retry] is a ghost button that triggers manual save flush
  • Persists until retry succeeds or new edit triggers auto-save

With Refine Panel Open (3-column layout)

+=================================================================================+
|  Pages    [+]  |  Root > Current Page     [✓] Saved  |  Refine with Agent      |
|  ---------------+                                      |  ----------------------|
|  ├── Arch       |  # Auth Architecture                |  [spinner] Architect   |
|  │   ├── Ovw    |                                      |  is refining...        |
|  │   └── Dec    |  This document outlines the          |                        |
|  ├── Req        |  OAuth 2.0 implementation...         |  (see panel detail     |
|  └── API        |                                      |   below)               |
|                  |                                      |                        |
|     240px        |        editor (flex-1)               |       360px            |
|                  |                                      |                        |
+=================================================================================+
  • Refine panel sits above editor in v1 source (not beside it)
  • v2 proposal: move to right column when viewport >= 1280px
  • Below 1280px, refine panel stays above editor (current v1 behavior)
  • Panel width: 360px fixed (w-[360px]), editor remains flex-1
  • Save indicator placement: in the 3-column layout, the save indicator stays inline with the breadcrumb row, right-aligned within the editor column (middle column). It does NOT extend into the refine panel column. The breadcrumb row is flex justify-between within the editor column only.

Refine Panel — Internal Layout

The refine panel is a right-side column with its own state machine. The wireframe must show all states, since this is where the agent-human interaction happens.

State: Idle (no active agent)

+-----------------------------------+
|  Refine with Agent                |
|  ---------------------------------|
|                                   |
|  [sparkle icon]                   |
|  An agent will review all pages   |
|  and suggest improvements.        |
|                                   |
|  What should it focus on?         |
|  +-------------------------------+|
|  | (optional instruction)        ||
|  +-------------------------------+|
|                                   |
|  [ Start Refine ]                 |
|                                   |
+-----------------------------------+
  • Panel header: text-sm font-semibold, top-left
  • CTA button: <Button> default variant, full-width
  • Instruction input: <Textarea> with placeholder, optional

State: Running

+-----------------------------------+
|  Refine with Agent          [stop]|
|  ---------------------------------|
|  [●] Architect                    |  <-- status-active-dot + agent name
|  is refining...                   |
|                                   |
|  Elapsed: 0:42                    |  <-- text-xs text-muted-foreground
|                                   |
|  Live output:                     |
|  +-------------------------------+|
|  | > Reading page tree...        ||  <-- terminal-bg, scrollable
|  | > Analyzing Architecture...   ||
|  | > Drafting proposals...       ||
|  +-------------------------------+|
|                                   |
+-----------------------------------+
  • Live output: mini terminal view, bg-terminal text-terminal-fg, max-h-48 overflow-y-auto
  • [stop] button: ghost variant, text-destructive on hover
  • Agent name: font-mono text-sm
  • Elapsed timer: updates every second via setInterval

State: Agent has questions

+-----------------------------------+
|  Refine with Agent                |
|  ---------------------------------|
|  Agent has questions              |
|                                   |
|  1. Should I restructure the      |
|     auth section or just refine   |
|     the existing text?            |
|  +-------------------------------+|
|  | (your answer)                 ||
|  +-------------------------------+|
|                                   |
|  2. Include code examples?        |
|  +-------------------------------+|
|  | (your answer)                 ||
|  +-------------------------------+|
|                                   |
|  [ Submit Answers ]  [ Stop ]     |
|                                   |
+-----------------------------------+
  • Questions rendered as numbered list
  • Each answer: <Textarea> auto-growing
  • Submit: default button; Stop: destructive-outline button
  • Keyboard: Cmd+Enter submits all answers

State: Completed (with proposals)

+-----------------------------------+
|  Refine with Agent       [dismiss]|
|  ---------------------------------|
|  Agent finished with 3 proposals  |
|                                   |
|  ┌─ Page: Auth Architecture ─────┐|
|  │ Expanded OAuth flow section   │|
|  │ with PKCE details             │|
|  │           [Accept] [Reject]   │|
|  └───────────────────────────────┘|
|                                   |
|  ┌─ Page: API Endpoints ─────────┐|
|  │ Added rate limiting section   │|
|  │           [Accept] [Reject]   │|
|  └───────────────────────────────┘|
|                                   |
|  ┌─ Page: Requirements ──────────┐|
|  │ Clarified NFR-3 latency req   │|
|  │           [Accept] [Reject]   │|
|  └───────────────────────────────┘|
|                                   |
|  [ Accept All ]  [ Dismiss All ]  |
|                                   |
+-----------------------------------+
  • Each proposal: card (bg-card border rounded-md p-3)
  • Card header: target page name in text-xs font-medium text-muted-foreground
  • Card body: proposal summary in text-sm
  • Accept: default button; Reject: ghost button
  • Bulk actions: Accept All (primary) + Dismiss All (ghost) at bottom
  • Accepted proposals: green checkmark overlay, card fades to opacity-50
  • Rejected proposals: strikethrough summary, card fades to opacity-30

State: Completed (no changes)

+-----------------------------------+
|  Refine with Agent       [dismiss]|
|  ---------------------------------|
|                                   |
|  Agent completed — no changes.    |
|  [dismiss] Cmd+Enter              |
|                                   |
+-----------------------------------+

State: Crashed

+-----------------------------------+
|  Refine with Agent                |
|  ---------------------------------|
|  [!] Agent crashed                |
|                                   |
|  [ Retry ]                        |
|                                   |
+-----------------------------------+
  • Error icon: AlertCircle, text-destructive
  • Retry button opens the spawn dialog again

Breadcrumb with Deep Nesting (ellipsis collapse)

Depth <= 3: show all segments

  Root > Architecture > Decisions

Depth > 3: collapse middle with ellipsis

  Root > [...] > API Endpoints > Rate Limiting
  • Root always visible (first segment, clickable)
  • [...] is a dropdown trigger button (not static text), styled as text-muted-foreground hover:text-foreground cursor-pointer
  • Last two segments always visible and clickable
  • Clicking [...] opens a <Popover> (shadcn/ui) anchored below the ellipsis:
  Root > [...] > API Endpoints > Rate Limiting
           |
           v
  +---------------------------+
  |  > System Design          |  <-- clickable, navigates
  |  > Backend Architecture   |  <-- clickable, navigates
  |  > API Layer              |  <-- clickable, navigates
  +---------------------------+
      bg-popover, shadow-md, rounded-md
      border border-border
      min-w-[180px]
  • Each row in the dropdown: px-3 py-1.5 text-sm hover:bg-accent cursor-pointer
  • Clicking a row navigates to that page AND closes the popover
  • Popover closes on outside click or Esc
  • Implementation: <Popover> + <PopoverTrigger> + <PopoverContent> from shadcn/ui
  • Separator character > uses text-muted-foreground mx-1 (ChevronRight icon, 12px)

Page Tree Sidebar Detail

+--------------------------+
|  Pages              [+]  |
|  [search filter...]      |
|                          |
|  ├── Architecture   [+]  |  <-- [+] on hover: create child of Architecture
|  │   ├── Overview        |  <-- active page: bg-accent
|  │   └── Decisions       |
|  ├── Requirements   [+]  |
|  └── API Design     [+]  |
|                          |
|  ≡ drag handle on hover  |
|                          |
+--------------------------+
     240px, border-right

Width rationale

192px is too narrow. At 12px indent per level, depth-3 nodes lose ~36px to indentation plus ~14px for the icon plus ~20px for the hover [+] button. That leaves ~122px for the page title — "Authentication Architecture" alone is ~180px at text-sm. Bump to 240px (w-60). Even at 240px, titles will truncate; the truncation + tooltip behavior below handles this.

Truncation + tooltip

All page titles render with truncate (CSS text-overflow: ellipsis). On hover, a <Tooltip> shows the full title after a 500ms delay. Implementation: wrap each node label in <TooltipTrigger> from shadcn/ui.

Node behavior

  • Header [+] always visible (new in v2) — creates page under root
  • Node [+] on hover only (unchanged from v1) — creates child of that node (contextual, not root-level)
  • FileText icon + truncated title per node
  • Active page: bg-accent rounded-md highlight
  • Indentation: 12px per depth level

Drag-and-drop reordering

Nodes are draggable to reorder within their sibling group or re-parent under a different node. Visual feedback:

|  ├── Architecture        |
|  │   ├── Overview        |
|  │   ┌─ ─ ─ ─ ─ ─ ─ ┐  |  <-- drop indicator (border-primary dashed)
|  │   └── Decisions       |
|  ├── Requirements        |
  • Drag handle: GripVertical icon, visible on hover (left of FileText icon)
  • Drop targets: between siblings (reorder) or onto a node (re-parent)
  • Drop indicator: 2px dashed border-primary line between nodes
  • Implementation: @dnd-kit/core + @dnd-kit/sortable (already tree-friendly)
  • On drop: call updatePage({ id, parentPageId, sortOrder }) mutation
  • Root page is not draggable (always first)

Search / filter

For initiatives with 20+ pages, the sidebar header includes a collapsible search filter:

+--------------------------+
|  Pages         [search][+]|
|  [filter query____]      |  <-- shown when [search] toggled
|                          |
|  ├── Auth Architecture   |  <-- matches highlighted
|  │   └── Auth Decisions  |
|                          |
+--------------------------+
  • Toggle: Search icon button in header, between title and [+]
  • Input: compact text field, text-xs, h-6, auto-focus on open
  • Filter: fuzzy substring match on page titles, case-insensitive
  • Matching nodes shown with all ancestors (preserves tree context)
  • Non-matching leaf nodes hidden; non-matching branches hidden if no descendants match
  • Esc clears filter and collapses the search input
  • Empty state: "No pages match" in text-muted-foreground text-xs

Keyboard navigation (new in v2)

  • Arrow Down / Arrow Up — move focus between visible tree nodes
  • Arrow Right on collapsed node — expand children
  • Arrow Left on expanded node — collapse; on leaf — move to parent
  • Enter — navigate to focused node (same as click)
  • Home / End — jump to first / last visible node
  • Focus ring: ring-2 ring-ring ring-offset-1 on the focused node row

Read-Only Lockout (Agent Refining)

When the refine agent is in running state, the editor should enter a read-only mode to prevent conflicting edits. The agent is rewriting page content server-side; concurrent user edits would be overwritten or cause merge conflicts.

+=================================================================================+
|  Pages    [+]  |  Root > Current Page                       |  Refine...        |
|  ---------------+  ┌─────────────────────────────────────┐   |                   |
|  ├── Arch       |  │ [lock] Editor locked while agent    │   |  [●] Architect   |
|  │   ├── Ovw    |  │        is refining content          │   |  is refining...  |
|  │   └── Dec    |  └─────────────────────────────────────┘   |                   |
|  ├── Req        |                                             |                   |
|  └── API        |  # Auth Architecture                       |                   |
|                  |                                             |                   |
|                  |  This document outlines the OAuth 2.0      |                   |
|                  |  implementation strategy for...            |                   |
|                  |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   |                   |
|     240px        |  text at reduced opacity (opacity-60)      |       360px       |
|                  |                                             |                   |
+=================================================================================+
  • Banner: bg-status-warning-bg text-status-warning-fg border border-status-warning-border with Lock icon (lucide-react), positioned above the title input
  • Editor: contenteditable="false", opacity-60, cursor-not-allowed
  • Title input: disabled, opacity-60
  • Banner dismisses automatically when agent finishes (state transitions away from running)
  • User can still navigate the page tree and read other pages while locked

Word Count / Reading Time

Persistent footer below the editor content area, right-aligned.

|                                                                     |
|  This document outlines the OAuth 2.0 implementation strategy...    |
|                                                                     |
|                                                 ~1,240 words · 5m  |
+---------------------------------------------------------------------+
  • Position: bottom-right of editor area, text-xs text-muted-foreground
  • Format: ~{count} words · {minutes}m (reading time at 250 wpm, rounded up)
  • Calculation: count words from Tiptap editor.getText() on each update (debounced 500ms)
  • Only shown when editor has content (hidden on empty/placeholder state)
  • Does not count content inside code blocks differently (plain word count)
  • In 3-column layout: stays within the editor column, does not leak into refine panel

Loading State

+=================================================================================+
|  +------------+              |  +--------------------------------------------+  |
|  |  ░░░░░░░░  |  skeleton    |  |  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  |  |
|  |  ░░░░░░░░  |  h-64 w-60   |  |  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  |  |
|  |  ░░░░░░░░  |              |  |  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  |  |
|  +------------+              |  +--------------------------------------------+  |
|     240px                    |              flex-1                               |
+=================================================================================+
  • Shown while rootPageQuery.isLoading
  • Skeleton shimmer components from @/components/Skeleton

Error State

+=============================================================================+
|                                                                             |
|                          [AlertCircle]                                      |
|                  Failed to load editor: <message>                           |
|                                                                             |
|              Make sure the backend server is running                         |
|              with the latest code.                                          |
|                                                                             |
|                          [ Retry ]                                          |
|                                                                             |
+=============================================================================+
  • text-destructive for error message
  • text-muted-foreground for help text
  • Retry button calls rootPageQuery.refetch()

Source

  • packages/web/src/components/editor/ContentTab.tsx
  • packages/web/src/components/editor/PageTree.tsx
  • packages/web/src/components/editor/TiptapEditor.tsx
  • packages/web/src/components/editor/RefineAgentPanel.tsx
  • packages/web/src/components/editor/DeleteSubpageDialog.tsx
  • packages/web/src/components/editor/PageTitleContext.tsx
  • packages/web/src/hooks/useAutoSave.ts

Design Review Notes

Critique and improvements applied during design review. Each item references the review criterion that prompted the change.

1. Sidebar width: 192px -> 240px

Problem. 192px is dangerously tight. At depth 3 with 12px indent per level, you burn 36px on indentation, 14px on the FileText icon, 20px on the hover [+] button, and 8px on left padding. That leaves ~114px for the title. A page named "Authentication Architecture Decisions" at text-sm (14px) needs roughly 280px. You would see "Authenticat..." which is garbage -- users cannot distinguish it from "Authentication" or "Authorization". 240px gives ~162px for the title, which still truncates long names but shows enough to differentiate. The truncation-plus-tooltip pattern catches the rest.

Trade-off. 48px wider sidebar steals from the editor. At 1280px viewport that is ~3.75% of screen width. Acceptable -- the editor still gets flex-1 and the content column never drops below ~680px even with the refine panel open (1280 - 240 sidebar - 360 refine = 680px editor).

2. Breadcrumb ellipsis: static text -> dropdown

Problem. The original spec said ... collapses middle segments and mentioned it was a "dropdown trigger," but the wireframe showed static text and the interaction was under-specified. If a user sees Root > ... > Rate Limiting they have no way to navigate to intermediate pages without going back to the tree and hunting.

Fix. Explicitly wireframed the [...] as a <Popover> trigger with the full hidden path rendered as clickable rows. Specified the component (shadcn/ui Popover), the styling, the close behavior, and the row interaction.

3. Save indicator in 3-column layout

Problem. If the save indicator is "top-right" of the editor area, and the refine panel opens as a third column, does "top-right" mean top-right of the entire viewport (overlapping the refine panel) or top-right of the editor column?

Fix. Clarified: the breadcrumb row with the save indicator is flex justify-between within the editor column only (the middle column). It does not bleed into the refine panel. The save indicator sits at the right edge of the breadcrumb row, which is bounded by the editor column width.

4. [+] button context

Problem. Two [+] buttons exist (sidebar header and per-node hover) but the original spec did not clearly distinguish their behavior. A user might expect the header [+] to create a sibling of the currently selected page, not a root-level page.

Fix. The spec now explicitly states: header [+] always creates under root, node hover [+] creates a child of that specific node. This is contextual by default on the node buttons. The header button is intentionally non-contextual (always root) to provide a reliable "new top-level page" entry point.

5. Drag-and-drop reordering

Problem. The original wireframe had no drag-and-drop. For a document management UI, reordering pages by editing sortOrder in a database is not a viable UX. Users need to drag.

Fix. Added a full drag-and-drop section: GripVertical handle on hover, drop indicators (dashed border-primary lines), re-parenting via drop-on-node, and the library choice (@dnd-kit). Specified the mutation call (updatePage({ id, parentPageId, sortOrder })).

6. Page tree search/filter

Problem. With 20+ pages (realistic for a detailed initiative), scanning a tree visually breaks down. There was no search.

Fix. Added a collapsible search filter in the sidebar header. Fuzzy substring match, ancestor preservation (so matching leaf nodes still show their tree path), and Esc to clear. The toggle button sits between the "Pages" title and the [+] button.

7. Read-only lockout during agent refine

Problem. When the refine agent is running, it rewrites page content server-side. If the user is simultaneously typing in the editor, the next server push will overwrite their changes with no warning. This is a data loss scenario.

Fix. Added a full "Read-Only Lockout" section. The editor goes contenteditable="false" with reduced opacity and a warning banner using status-warning tokens. The lockout auto-clears when the agent finishes. Users can still read and navigate during the lock.

8. Refine panel internal layout

Problem. The original wireframe showed the refine panel as a 320px column with "[spinner] Architect is refining..." and nothing else. The actual implementation (verified in RefineAgentPanel.tsx) has five distinct states: idle (spawn dialog), running (spinner), waiting (question form), completed (change set / proposals), and crashed (error + retry). None of these were wireframed.

Fix. Added wireframes for all five states with full internal layout: idle with instruction textarea, running with live output terminal, question form with numbered questions and answer textareas, completed with per-proposal Accept/Reject cards plus bulk actions, and crashed with error + retry button. Also bumped the panel from 320px to 360px because the proposal cards with Accept/Reject buttons need the room.

9. Word count / reading time

Problem. Content pages had no feedback on document length. For planning documents that agents will consume, knowing that a page is 3,000 words vs 300 words matters for understanding agent processing time and content density.

Fix. Added a persistent ~{words} words · {minutes}m footer below the editor, right-aligned, in text-xs text-muted-foreground. Calculation is debounced off editor.getText(). Hidden on empty state.

10. Slash command menu

Problem. The placeholder says "use / for commands" but the wireframe never showed what the command menu looks like. A developer implementing this has no visual spec for the floating popover.

Fix. Added a full wireframe of the slash command menu: floating popover anchored to cursor, all command options listed with icons, keyboard navigation behavior, styling tokens, and scroll behavior for long menus.

Open questions for next review

  1. Collaborative editing. If two humans (or a human + agent) could theoretically edit the same page, should the content tab show presence indicators (avatars/cursors)? The current lockout approach sidesteps this but is not a long-term solution.

  2. Page versioning. Should pages have a revision history? The refine agent creates proposals, but if the user manually edits a page and breaks it, there is no undo beyond browser undo. A page history (snapshots on save) would mitigate this.

  3. Tree collapse state persistence. Should expanded/collapsed tree nodes persist across page reloads? Currently the tree is always fully expanded. For deep trees, remembering collapse state in localStorage or URL state would reduce visual noise.

  4. Max nesting depth. Should there be a hard limit on page nesting depth? At 12px indent per level, depth 8 consumes 96px of the 240px sidebar. Consider capping at depth 5 or 6 and showing a warning if users try to nest deeper.