15 KiB
Phase 9 — Advanced Tools Design
Companion to the parent plan:
docs/superpowers/plans/2026-06-05-react-ui-redesign.md(Phase 9 is sketched at high level; this spec locks architecture, file map, and contracts so it can be planned task-by-task.)
Date: 2026-06-05
Phase: 9 of 10 (React + shadcn/ui UI redesign)
Tag (on completion): phase-9-advanced-tools
1. Goal & Non-Goals
Goal: Add 10 advanced tools to the React renderer. Group 1: Standalone dialogs (ASCII generator, Table generator, Word export, Find-in-files). Group 2: Global overlays (Zen mode, REPL, Print preview). Group 3: Editor/sidebar integrations (Minimap, Breadcrumbs-with-symbols, Git status). All triggered via the Phase 6 command store.
Non-goals (Phase 9):
- True undo/redo stack (still not in scope)
- Snippet/template library
- Custom REPL with full JS eval (we chose markdown snippet preview for safety)
- Custom diff/merge UI (just shows git status, no in-app diffing)
- Plug-in extensions
- Multi-cursor editing
- LSP / language server integration
Decision summary (from brainstorming):
- REPL = markdown snippet preview (no JS eval — safe in renderer)
- Word export =
.docxviadocxlib in renderer, with both Standard and Custom .dotx template modes - Find-in-files = recursive search with result navigation (new IPC, regex support)
2. Architecture
2.1 Three mount strategies
| Category | Mount | Examples |
|---|---|---|
| Dialogs | <ModalLayer /> (Phase 7 pattern) |
ASCII gen, Table gen, Word export, Find-in-files |
| Global overlays | Top-level in App.tsx (like <Toaster />) |
Zen mode, REPL panel, Print preview |
| Editor/sidebar integrations | Extend existing components | Minimap, Breadcrumbs, Git status |
The ModalLayer is the dispatcher for dialogs. The global overlays are mounted directly in App.tsx because they need to participate in the top-level layout (full-window, or pinned to the bottom).
2.2 ModalState union extension
The Phase 7 ModalState discriminated union (9 kinds) gets 4 new kinds for the new dialogs:
export type ModalState =
| { kind: null }
| { kind: 'export-pdf'; props: { sourcePath: string } }
| { kind: 'export-docx'; props: { sourcePath: string } }
| { kind: 'export-html'; props: { sourcePath: string } }
| { kind: 'export-batch'; props: { sourcePaths: string[] } }
| { kind: 'export-word'; props: { sourcePath: string } } // NEW
| { kind: 'ascii-generator' } // NEW
| { kind: 'table-generator' } // NEW
| { kind: 'find-in-files' } // NEW
| { kind: 'settings' }
| { kind: 'about' }
| { kind: 'welcome' }
| { kind: 'confirm'; props: ConfirmProps };
Three of the four new kinds (ascii, table, find-in-files) take no props — they read from the active buffer via useExportSource (for ascii/table) or from useFileStore.rootPath (for find-in-files). export-word takes sourcePath like the other export dialogs.
2.3 New commands in command store
| Command ID | Trigger | Opens |
|---|---|---|
tools.ascii |
new | AsciiGeneratorDialog |
tools.table |
new | TableGeneratorDialog |
tools.exportWord |
new | WordExportDialog |
tools.findInFiles |
new | FindInFilesDialog |
tools.repl |
new | toggles REPL panel |
view.zenMode |
existing (Phase 6) | toggles Zen mode overlay |
file.print |
new | opens PrintPreview overlay |
git.refresh |
new | re-fetches git status |
All registered in src/renderer/lib/commands/register-menu-commands.ts.
2.4 New IPC surface
// src/renderer/lib/ipc.ts (additions)
ipc.file.search({ rootPath, query, isRegex, caseSensitive }: {
rootPath: string;
query: string;
isRegex: boolean;
caseSensitive: boolean;
}): Promise<IpcResult<Array<{ filePath: string; line: number; content: string }>>>;
ipc.file.gitStatus({ rootPath }: { rootPath: string }): Promise<IpcResult<Array<{ filePath: string; status: 'modified' | 'added' | 'deleted' | 'untracked' }>>>;
ipc.file.print({ html }: { html: string }): Promise<IpcResult<void>>;
ipc.file.writeBuffer({ path, buffer }: { path: string; buffer: Uint8Array }): Promise<IpcResult<void>>;
The main process counterparts are added to src/main.js (or a new src/main/files-search.js, etc.). For Phase 9, the spec covers the renderer-side design; main-process IPC handlers are assumed to follow the same ipcMain.handle pattern as the existing handlers.
2.5 Settings additions (useSettingsStore)
// zod schema additions:
docxCustomTemplatePath: z.string().nullable().default(null), // path to user .dotx file
replOpen: z.boolean().default(false), // REPL panel visibility
breadcrumbSymbols: z.boolean().default(true), // breadcrumbs show code symbols
// minimap already exists from Phase 7 (useSettingsStore.minimap: z.boolean().default(true))
The minimap setting from Phase 7 is now wired (not just stored).
3. File Map
3.1 Created files
Dialogs (in src/renderer/components/modals/):
AsciiGeneratorDialog.tsx— input textarea, font select, output preview with copy buttonTableGeneratorDialog.tsx— rows × cols inputs, header checkbox, output previewWordExportDialog.tsx— template select (Standard / Custom .dotx), options, preview, exportFindInFilesDialog.tsx— query input, regex/case toggles, results list with click-to-navigate
Global overlays (in src/renderer/components/tools/):
ReplPanel.tsx— bottom-pinned, textarea + rendered previewPrintPreview.tsx— full-window print preview
Editor/sidebar integrations:
src/renderer/components/layout/ZenMode.tsx— wraps/controls the editor in zen mode (or modifyAppShell.tsxto hide chrome)src/renderer/components/sidebar/GitStatusPanel.tsx— file list with status badges
Lib:
src/renderer/lib/docx-export.ts— renderer-sidedocxlib integration (markdown → Blob)src/renderer/hooks/use-zen-mode.ts— small hook that readsuseSettingsStore.zenMode
Modifications:
src/renderer/App.tsx— mount<ReplPanel />and<PrintPreview />alongside<ModalLayer />and<Toaster />src/renderer/components/layout/AppShell.tsx— whenzenMode === true, hide all chrome except the editorsrc/renderer/components/editor/CodeMirrorEditor.tsx— add@codemirror/minimap(or@replit/codemirror-minimap) whenuseSettingsStore.minimapis truesrc/renderer/components/layout/Breadcrumb.tsx— extend to show symbols (headings, code blocks) using@codemirror/langs-dataor a simple markdown AST walksrc/renderer/components/sidebar/Sidebar.tsx— add a "Git" tab to the existing tab listsrc/renderer/stores/settings-store.ts— add 3 new fieldssrc/renderer/lib/validators.ts— add 3 new fields tosettingsSchemasrc/renderer/lib/ipc.ts— add 4 new IPC methodssrc/renderer/lib/commands/register-menu-commands.ts— add 8 new commandssrc/main.js(or split files) — add 4 main-process IPC handlers
Tests:
tests/component/tools/ReplPanel.test.tsx— smoke testtests/component/tools/PrintPreview.test.tsx— smoke testtests/component/modals/AsciiGeneratorDialog.test.tsx— 3 teststests/component/modals/TableGeneratorDialog.test.tsx— 3 teststests/component/modals/WordExportDialog.test.tsx— 4 teststests/component/modals/FindInFilesDialog.test.tsx— 4 teststests/component/sidebar/GitStatusPanel.test.tsx— 3 teststests/component/layout/Breadcrumb.test.tsx— extend with symbols testtests/integration/phase9-tools-smoke.test.tsx— 6 tests (one per command-triggered tool)
4. Data Flow
4.1 Word export (most complex)
- User triggers
tools.exportWord→registerMenuCommandsopens the export-word modal viauseAppStore.openModal('export-word', { sourcePath: activeTabId }) - Wait — we need a new modal kind
export-wordin theModalStateunion. Add it. WordExportDialog(mounted by ModalLayer) shows:- Template select: "Standard (bundled)" or "Custom .dotx (your file)" — pre-populated with
useSettingsStore.docxCustomTemplatePath - "Choose custom template..." button (file picker, saves to settings)
- Options: embed images (checkbox), include front matter (checkbox)
- Preview area: shows the generated docx structure (sizes, table of contents)
- Template select: "Standard (bundled)" or "Custom .dotx (your file)" — pre-populated with
- On submit:
- Renderer calls
lib/docx-export.ts#generateDocx(source, templatePath, options)which uses thedocxlib - The lib takes a
DocumentAST and produces a Blob - ASCII tables are converted to monospace
<w:r>runs (usingapplyAsciiTransformfrom Phase 7) - Images are extracted from markdown and embedded as base64
- If a custom .dotx path is set, the renderer reads it via
ipc.file.readand applies its styles (viadocxlib's style support) - User picks output path via
ipc.app.showSaveDialog - Writes Blob via
ipc.file.writeBuffer(new) - Toast success/failure
- Renderer calls
4.2 Find-in-files
- User triggers
tools.findInFiles→ ModalLayer opensFindInFilesDialog - User types query, picks regex/literal, case-sensitive toggle
- On submit:
ipc.file.search({ rootPath: useFileStore.rootPath, query, isRegex, caseSensitive }) - Backend walks the disk recursively, applies regex/literal match, returns
Array<{ filePath, line, content }> - Dialog shows result list (file:line:content, clickable)
- Click a result:
useFileStore.openFile(filePath)if not already open- Set the cursor in the editor to the matched line
- Close the dialog
4.3 REPL (markdown snippet preview)
- User triggers
tools.repl→ togglesuseSettingsStore.replOpen(or a new dedicateduseReplStore) ReplPanel(mounted in App.tsx) is visible whenreplOpen === true- The panel has:
- Top half: textarea (user types/pastes markdown)
- Bottom half: rendered preview (uses
lib/markdown.tsfrom Phase 1 + DOMPurify)
- Updates are debounced (300ms) for the preview
- Pure renderer-side, no IPC
4.4 Zen mode
- User triggers
view.zenMode→ togglesuseSettingsStore.zenMode AppShellreadszenModefrom store; when true, hides all chrome (header, tabs, toolbar, breadcrumb, status bar, sidebar)- Editor goes fullscreen
- Pressing Esc exits zen mode (keydown listener in
use-zen-modehook)
4.5 Print preview
- User triggers
file.print→ opensPrintPreview(full-window overlay) - The preview renders the current buffer's content as it would appear in print
- Two buttons: "Print" (calls
ipc.file.print({ html })which opens native print dialog) and "Close"
4.6 Minimap
useSettingsStore.minimap(existing setting) is read byCodeMirrorEditor- When true, the editor's extension includes
@replit/codemirror-minimap(or a simpler custom implementation) - The minimap shows a shrunk version of the document on the right side of the editor
- Toggling the setting adds/removes the extension dynamically
4.7 Breadcrumbs-with-symbols
- The current
Breadcrumbshows the file path. Extend it to also show markdown symbols (headings, code blocks) - Use a simple AST walk of the current buffer to extract heading levels
- Show as a path-like navigation:
file.md > H1: Title > H2: Section > #line - The
useSettingsStore.breadcrumbSymbolstoggle (default true) controls whether symbols are shown
4.8 Git status
- On sidebar Git tab open, fetch
ipc.file.gitStatus({ rootPath: useFileStore.rootPath }) - The main process runs
git status --porcelain(or equivalent) and returns the list - Show file list with status badges (M, A, D, ?)
- Click a file opens it via
useFileStore.openFile git.refreshre-fetches
5. Error Handling
- Word export failures:
lib/docx-export.tscatchesdocxlib errors → toast.error - Word export missing .dotx: if custom template path is set but file doesn't exist, show inline banner in the dialog
- Find-in-files failures: if
ipc.file.searchthrows (regex syntax error, IO error), show inline banner in the dialog - REPL: no IPC, no error handling needed
- Git status: if folder isn't a git repo, return empty array. Panel shows "Not a git repository" message
- Print: if
ipc.file.printfails (no printer, user cancels), toast.error or silent close - Minimap, Breadcrumbs: renderer-side, no errors
6. Testing Strategy
- Unit tests:
lib/docx-export.ts(markdown → docx conversion, ASCII handling),lib/ascii-table.ts(already tested) - Component tests: 1-2 smoke tests per new component
- Integration test:
phase9-tools-smoke.test.tsxcovering the 6 command-triggered tools (dispatch opens correct modal/overlay) - Editor integrations: minimap toggle, breadcrumbs render with symbols, git status panel render
- Target: +30-40 new tests, total ~290
7. Risks & Open Questions
Risks:
docxlib bundle size (~500KB). Acceptable for a desktop app but worth noting. Lazy-load if it becomes a concern.- Word export custom template parsing — the
.dotxformat is a zip with XML inside. Thedocxlib may not fully support reading existing templates. v1 may need a simpler "styles only" extraction. - Find-in-files regex compilation — invalid regex would throw. Validate on the renderer before calling IPC.
- Minimap performance — for large files, the minimap can slow down scrolling. CodeMirror's built-in minimap has performance options to configure.
Open questions (deferrable):
- REPL persistence — should the textarea content survive across modal close/reopen? → Decision: NO for v1. Keep simple.
- Git status auto-refresh — should it auto-refresh every N seconds? → Decision: NO for v1. Manual via
git.refreshcommand. - Find-in-files result count limit — what if there are 10,000 matches? → Decision: limit to 500 results for v1, show "X more matches" message.
8. Out of Scope (deferred to Phase 10 or later)
- Custom undo/redo (still not in scope)
- Snippet/template library
- Multi-cursor editing
- LSP / language server
- Phase 10 will delete legacy files including the old
src/print-preview.js,src/wordTemplateExporter.js,src/welcome.js,src/zen-mode.js, etc.
9. Success Criteria
Phase 9 is complete when:
- All 10 features implemented and accessible via the command store
lib/docx-export.tsgenerates valid.docxfiles- Find-in-files returns results from a recursive disk walk
- REPL renders markdown snippet previews
- Zen mode hides all chrome and Esc exits
- Print preview opens native print dialog
- Minimap appears when
useSettingsStore.minimapis true - Breadcrumbs show symbols by default
- Git status panel shows modified/added/deleted/untracked files
- ~+30-40 new tests, total ~290
npx vite buildsucceeds- Branch tagged
phase-9-advanced-toolsand pushed to origin