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)
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) [✓] Savedindicator 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-placeholderwithemptyEditorClass - 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.,/headshows only headings) Arrow Up/Arrow Downto navigate;Enterto select;Escto 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-autowith 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...
Loader2icon withanimate-spin,text-muted-foreground- Shown while
isSavingorupdateInitiativeMutation.isPending
Saved (checkmark, fading)
Root > Architecture > Current Page [✓] Saved
- Green checkmark icon,
text-green-500 - Fades to
opacity-0after 2s viatransition-opacity duration-1000 - Resets to full opacity on next save cycle
Failed (error with retry)
Root > Architecture > Current Page [!] Failed [retry]
AlertCircleicon,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 remainsflex-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-betweenwithin 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-destructiveon 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+Entersubmits 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
Rootalways visible (first segment, clickable)[...]is a dropdown trigger button (not static text), styled astext-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
>usestext-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-mdhighlight - 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:
GripVerticalicon, visible on hover (left of FileText icon) - Drop targets: between siblings (reorder) or onto a node (re-parent)
- Drop indicator: 2px dashed
border-primaryline 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:
Searchicon 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
Escclears 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 nodesArrow Righton collapsed node — expand childrenArrow Lefton expanded node — collapse; on leaf — move to parentEnter— navigate to focused node (same as click)Home/End— jump to first / last visible node- Focus ring:
ring-2 ring-ring ring-offset-1on 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-borderwithLockicon (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-destructivefor error messagetext-muted-foregroundfor help text- Retry button calls
rootPageQuery.refetch()
Source
packages/web/src/components/editor/ContentTab.tsxpackages/web/src/components/editor/PageTree.tsxpackages/web/src/components/editor/TiptapEditor.tsxpackages/web/src/components/editor/RefineAgentPanel.tsxpackages/web/src/components/editor/DeleteSubpageDialog.tsxpackages/web/src/components/editor/PageTitleContext.tsxpackages/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
-
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.
-
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.
-
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
localStorageor URL state would reduce visual noise. -
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.