refactor: view-only multi-window app (tabs → windows, editor removed) - #7
Merged
Conversation
Introduces the tooling foundation for the view-only multi-window refactor. No product code changes beyond Prettier auto-formatting. - ESLint flat config (eslint.config.js) with typescript-eslint, react, react-hooks, jsx-a11y, and eslint-config-prettier. Noisy existing rules are downgraded to warnings with a TODO to tighten once the refactor replaces their source (tabs, editor, etc.). - Prettier config with singleQuote/semi/trailingComma=all/100-col. Ignores src-tauri/*.rs, installer XML, and binary assets. - Vitest with jsdom + Testing Library. Placeholder harness test at src/__tests__/harness.smoke.test.ts (replaced in Phase 6). - New scripts: lint, lint:fix, format, format:check, typecheck, test:unit, test:unit:watch, test:coverage, test:all. - GitHub Actions CI (ci.yml) runs on pull_request + push-to-main: frontend job (typecheck, lint, format:check, vitest, playwright) and rust job (cargo check, clippy, test) on Ubuntu. - Narrowly suppresses clippy::map_clone at src/lib.rs:831 (to be fixed naturally when lib.rs is rewritten in Phase 3/4). - tsconfig types include vitest/globals and @testing-library/jest-dom so tests typecheck under the existing tsc gate. - Prettier-formatted 58 files (docs, themes, TS sources). No logic changes. - .gitignore adds playwright-report/, test-results/, coverage/. Pre-existing tracked artifacts removed from the index. All of typecheck, lint, format:check, test:unit, and test:e2e (scroll-sync spec) pass locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrites the frontend as a one-window-per-file view-only app.
Phase 2 of 8 in the multi-window refactor. Rust backend has NOT
been updated yet (Phase 3) so Tauri builds are expected to be
broken until then; web-mode and all frontend gates (typecheck,
lint, format, build, unit tests) pass.
New structure (src/windows/):
- WelcomeWindow.tsx — recents grid + Open/Help buttons, empty
state, clear-all, per-item remove. Rendered when no `?path=`
query param is present and no file has been opened yet.
- ViewerWindow.tsx — read-only viewer with toolbar (zoom,
theme, sidebar toggle, auto-resize toggle, export HTML/PDF,
help icon) + footer (word/char/reading time). Adapted from
the old DetachedWindow with all save/edit UI stripped.
- WindowRouter.tsx — picks welcome vs viewer from URL param
and handles the in-place welcome→viewer transition when a
file is opened into the `main` window.
New shared utilities:
- hooks/usePreferences.ts — global prefs (theme, zoom,
sidebarOpen, sidebarWidth, autosize) under
localStorage[markdoc-preferences]. Prefs do not propagate
live between windows; each new window reads them as starting
values.
- utils/recentFiles.ts — typed recents store backed by
localStorage[markdoc-recent-files] (existing key, legacy
string entries transparently upgraded). Cap 20, most-recent
first, get/add/remove/clear API.
- utils/openFileInWindow.ts — central routing helper. Delegates
label allocation to the (yet-to-be-built) Rust command
open_file_in_window(path); web-mode mock emulates this fully.
Major simplifications:
- App.tsx shrinks from ~1400 → 116 lines. Just mounts
WindowRouter, subscribes to OS theme, listens for
file://open-request, drains get_pending_opened_files on
launch, and destroys the window on menu://file/close-window.
- Viewer.tsx prop surface reduced to {content, theme,
sidebarOpen, sidebarWidth, onSidebarResize}. Outline sidebar,
Prism highlighting, copy buttons, link interception,
scroll-to-top preserved.
- Footer.tsx drops lastSavedAt/isDocumentOpen; self-hides on
empty content.
- useWindowResize.ts reduced to {autosize, onToggleAutosize}.
- Platform mocks rewritten for the new command surface:
open_file_in_window, list_open_file_windows, close_file_window,
get_pending_opened_files, export_*, get_app_version. Removed
mocks for all document/tab/detach commands.
- types/index.ts purged of Document/Tab/Detach types; added
ThemeName, Preferences, RecentFileEntry, WindowLabel,
OpenFileWindow.
Deleted (all editor / tab / sync-scroll infrastructure):
- components: Editor, TabBar, TabScrollControls, OpenTabsDropdown,
PerTabToolbar, DetachedWindow, RecentFilesDropdown
- hooks: useSyncScroll, useSyncScrollSimple
- utils: fileOpening, lineMapping, markdownLinePlugin
- entry: detached.tsx, detached.html (plus Vite rollup input)
Known expected breakage (fixed in later phases):
- tests/e2e/scroll-sync.spec.ts fails at runtime — Phase 6 deletes it.
- npm run tauri:dev / tauri:build broken until Phase 3 lands the
new command surface on the Rust side.
- CodeMirror packages still listed in package.json — Phase 7.
Gates:
- npm run typecheck: clean
- npm run lint: 0 errors, 40 warnings (was 126)
- npm run format:check: clean
- npm run test:unit: 1/1
- npm run build: 629 kB bundle (was ~1 MB)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3+4: rebuilds the Rust backend around a simple WindowRegistry
(canonical PathBuf <-> WindowLabel map) and rewrites the native menu
structure for a view-only multi-window app.
Rust backend:
- New src-tauri/src/window_registry.rs replaces document.rs. Lean
HashMap-based path<->label map with a monotonic viewer-<N>
allocator. 7 unit tests cover register/release/lookup invariants.
- New commands: open_file_in_window, list_open_file_windows,
close_file_window, refresh_menus (for native Open Recent menu
population from frontend-side localStorage).
- open_file_in_window canonicalises the path, focuses an existing
window if the path is already open, adopts the main window
(welcome -> viewer) if it's empty via viewer://open-path, or
spawns a fresh viewer-<N> window.
- Removed: update_document_content, mark_document_saved,
update_document_file_path, update_menu_state,
set_edit_menu_visible, reorder_tabs, detach_document,
reattach_document, get_detached_windows, create_document,
set_active_document, get_active_document_id, get_document,
get_all_documents, close_document, is_menu_ready,
respond_to_close_request.
- RunEvent::Opened and tauri-plugin-single-instance now both route
every incoming path through open_file_in_window.
RunEvent::Reopen (macOS dock-click) spawns a fresh welcome
window when no windows are visible.
- WindowEvent::Destroyed releases registry entries and rebuilds
the Window menu.
Native menu (restructured for view-only):
- File: Open (Cmd+O), Open Recent [dynamic], Close Window
(Cmd+W), Export (HTML/PDF with Cmd+Shift+H/P).
- Edit: Copy, Select All (native roles only).
- View: Zoom In/Out/Reset (Cmd+=/-/0), Theme [5 options],
Toggle Sidebar (Cmd+\\), Toggle Auto-resize.
- Window: Minimize, Maximize, dynamic list of open file windows
(click focuses the target window directly via set_focus).
- Help: User Guide.
- File > New/Save/Save As removed. EDIT menu Undo/Cut/Paste
removed (not meaningful in a view-only app).
Capabilities (tauri.conf.json):
- Added label "main" to the single app.windows entry.
- Replaced detached-capability (glob detached_*) with
viewer-capability (glob viewer-*).
- Dropped fs:allow-write-text-file and
core:webview:allow-webview-close from main-capability
(view-only; export writes via dialog:allow-save only).
- Added core:window:allow-show / allow-unminimize /
allow-set-focus for routing focus-existing behaviour.
- Removed stale src-tauri/capabilities/detached.json.
Frontend patches (small, complete the Rust<->TS contract):
- WindowRouter listens for viewer://open-path to adopt the main
window when Rust routes a file there, and for
menu://help/user-guide and menu://file/clear-recent.
- WelcomeWindow listens for menu://file/open (native Cmd+O).
- ViewerWindow listens for menu://file/open (open picker + route)
and accepts onOpenUserGuide prop from WindowRouter. Removed the
broken invoke('open_user_guide') stub from the help button.
- recentFiles.ts calls invoke('refresh_menus', { recents }) after
every add/remove/clear so the native Open Recent submenu stays
in sync. New syncRecentFilesMenu() helper called on mount pushes
the initial list.
CI:
- Removed the -A clippy::map_clone suppression now that the
offending code is gone.
Gates:
- cargo check --locked: clean
- cargo clippy --locked -- -D warnings: clean (no suppressions)
- cargo test --locked: 7/7 window_registry tests pass
- npm run typecheck: clean
- npm run lint: 0 errors, 40 warnings
- npm run format:check: clean
- npm run test:unit: 1/1
- npm run build: 629 kB bundle
Known remaining TODO (tracked for later phases):
- PredefinedMenuItem::bring_all_to_front not yet in tauri 2.8.5
(muda 0.17 only); omitted from Window menu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- tauri.conf.json fileAssociations.role: "Editor" -> "Viewer".
Propagates to macOS CFBundleTypeRole ("Viewer") and the Linux
.desktop file generated by Tauri. Shows up in Finder "Get Info"
and Gnome file manager dialogs.
- WiX ApplicationDescription for the MSI installer drops
"and editor", now reads "MarkDoc - A simple markdown viewer by
Stravica". This is what Windows shows in "Default Apps" and the
"Open With" > "Choose another app" dialog.
Matches the product repositioning done in Phases 2-4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 6: adds a proper test suite for the new per-window viewer app
and replaces the obsolete scroll-sync e2e.
Vitest unit (9 files, 93 tests, ~1s total):
- utils: recentFiles, openFileInWindow, linkHandler, pdfExport,
fileUtils. Each covers its public API (round-trips, guards,
mutation + invoke('refresh_menus') sync, legacy format migration).
- hooks: useZoom, useMarkdownTheme, usePreferences,
useDocumentOutline. Each covers defaults, state transitions,
persistence, and hierarchy extraction.
- Coverage: src/hooks 98.88% lines, src/utils 87.09% lines.
Playwright e2e (8 specs, 16 tests, ~4s total, web-mode harness):
- welcome: empty state, Open File transition, Clear recents.
- viewer-open: welcome->viewer, duplicate-focus dedupe,
menu://file/open routing.
- theme: switcher cycles through all five, persists across reloads.
- zoom: toolbar buttons + menu-driven shortcuts + clamp.
- sidebar: toolbar toggle + Ctrl/Cmd+\\ shortcut.
- export: HTML export fires export_html_command with correct args.
- help: welcome->user-guide and viewer->user-guide transitions.
- autoresize: toolbar toggle surfaces in the mock size call.
Rust (window_registry): 10 tests (7 pre-existing + 3 new):
- Label allocator does not reuse after release (monotonic).
- Canonical path collapsing via `..` traversal maps to the same
registry entry.
- Symlink canonical collapse routes both the link and the target
to the same registry entry.
Mock observability (src/platform/web.ts):
- Added `MockBackend.calls: Array<{cmd, args}>` reset by `reset()`
so e2e can assert command call sites (e.g., export args).
- Added `refresh_menus` as a recorded no-op so the frontend's
automatic menu sync doesn't blow up web-mode.
Deleted:
- src/__tests__/harness.smoke.test.ts (Phase 1 placeholder)
- tests/e2e/scroll-sync.spec.ts (editor/split-pane is gone)
Gates:
- npm run typecheck: clean
- npm run lint: 0 errors, 36 warnings (was 40)
- npm run format:check: clean
- npm run test:unit: 93/93
- npm run test:coverage: 91% overall, 99% hooks, 87% utils
- npm run test:e2e: 16/16 (4s)
- cargo test --locked: 10/10
Noted for Phase 7: extractRenderedHtml in pdfExport.ts is dead code
(legacy alt-path, not wired into ViewerWindow) and pulls coverage
down to 50% on that file; delete during the dep cleanup pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 7: package cleanup and safe version bumps after the refactor has settled. Removed (dead) dependencies: - @codemirror/* (10 packages — autocomplete, basic-setup, commands, lang-markdown, language, lint, search, state, theme-one-dark, view). Editor was deleted in Phase 2. - @uiw/react-tabs-draggable. Tabs UI is gone. - tailwindcss, @tailwindcss/postcss, autoprefixer, postcss. The Tailwind toolchain was never actually wired in — @apply / @tailwind directives are absent everywhere. Removed postcss.config.js and tailwind.config.js. Dead code: - src/utils/pdfExport.ts: deleted legacy extractRenderedHtml function (~210 lines). Only generatePdfHtml is referenced by ViewerWindow. Bumped (safe patch/minor): - @tauri-apps/api 2.8.0 -> 2.10.1, @tauri-apps/cli 2.8.4 -> 2.10.1 - plugin-dialog 2.4.0 -> 2.7.0, plugin-fs 2.4.2 -> 2.5.0 - plugin-opener 2.5.0 -> 2.5.3, plugin-os 2.3.1 -> 2.3.2 - react 19.2.0 -> 19.2.5, react-dom 19.2.0 -> 19.2.5 - @types/react 19.2.2 -> 19.2.14, @types/react-dom 19.2.2 -> 19.2.3 - vite 7.1.10 -> 7.3.2, @vitejs/plugin-react 5.0.4 -> 5.2.0 - @playwright/test + playwright 1.57.0 -> 1.59.1 - markdown-it 14.1.0 -> 14.1.1, isomorphic-dompurify 2.29.0 -> 2.36.0 - @types/prismjs 1.26.5 -> 1.26.6 - npm audit fix pulled in transitive security patches (picomatch ReDoS, rollup path traversal). 0 vulnerabilities remaining. Skipped (major bumps, require separate review): - typescript 5.9 -> 6.0 - vite 7 -> 8 (+ @vitejs/plugin-react 5 -> 6) - isomorphic-dompurify 2 -> 3 - eslint 9 -> 10 (held at 9 by eslint-plugin-jsx-a11y peer range) Rust: - cargo update full refresh. tauri 2.8.5 -> 2.10.3 (minor within 2.x), plus transitive refreshes across wry, tao, muda, zbus, tokio, serde_json, etc. No 2.x -> 3.x jumps. Release workflow: - .github/workflows/release.yml gains a `verify` job that runs typecheck, lint, format:check, unit tests, e2e tests, and the full Rust check/clippy/test suite on Ubuntu before the matrix `build-tauri` job starts. build-tauri now needs [prepare-release, verify]. Prevents shipping a broken release. Gates (post-cleanup, unchanged or improved): - typecheck: clean - lint: 0 errors, 36 warnings (unchanged) - format:check: clean - test:unit: 93/93 - test:coverage: 96.66% stmts / 90.59% branches / 97.91% func (up from 91% thanks to dead code removal) - test:e2e: 16/16 - cargo check --locked: clean - cargo clippy --locked -- -D warnings: clean - cargo test --locked: 10/10 - cargo check --locked --release: clean (release profile) Bundle size is effectively unchanged (~630 kB min, 204 kB gzip) — CodeMirror was already being tree-shaken out. Prism language packs are the next lever if chunk-size warnings become actionable; deferred until a real load-time complaint surfaces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 8: aligns development notes with the actual shipped shape after Phases 1-7. - Project Overview now describes MarkDoc as a view-only markdown reader with one window per file. - Technology Stack reflects current versions (Tauri 2.10, React 19, TypeScript 5.9, Vite 7, Vitest + Playwright). - New "Architecture" section describes the multi-window model, file-open routing through the Rust `open_file_in_window` command, global preferences, and the WindowRegistry. - Native Menus section replaces the old FILE/EDIT/VIEW description with the new File/Edit/View/Window/Help layout including dynamic Open Recent + Window lists and the menu:// event convention. - Rust Commands section documents the slim post-refactor command surface (get_app_version, open_file_in_window, list_open_file_windows, close_file_window, refresh_menus, export_*, get_pending_opened_files). - Drops all references to Editor, tabs, detached windows, session restoration, saved state, sync scroll, and the old menu event names. - New "Testing" section describes the web-mode harness (__MARKDOC_MOCK__), stable data-testid conventions, and the test coverage picture (93 unit / 16 e2e / 10 Rust). - New CI/CD section describes the `ci.yml` PR workflow and the `verify` gate inserted into `release.yml`. - Pre-release smoke checklist rewritten for the new architecture. - Known Issues updated — removed Tauri close-handler gotchas (no longer applicable in view-only), added deferred major bumps (TS 6, Vite 8, ESLint 10, DOMPurify 3) and the single-instance plugin caveat. - Windows File Associations section updated to reflect the Viewer role (was Editor). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two user-reported issues from the v0.1.5 mac build:
1. **Welcome window had no styling.** The Phase 2 refactor shipped
`WelcomeWindow` with structural classes (`welcome-root`,
`welcome-title`, `welcome-recents-list`, …) but no matching CSS.
This commit adds a full, theme-aware stylesheet:
- Centered 560px column with 64px top padding
- Bold 40px title, muted subtitle, monospaced version line
- Primary blue "Open File…" button + secondary outlined User
Guide button with hover/active/focus-visible states
- Uppercase-tracked "Recent Files" header with a "Clear all"
link aligned right
- Empty-state panel with dashed border
- Per-item rows: filename (bold), parent directory
(monospaced, `~/`-collapsed for $HOME), and relative time
- Trash × button that fades in on hover
- Missing `.py-4` utility added so viewer content gets proper
vertical padding
Also adjusted WelcomeWindow to render a tidy `displayPath`
(dirname, `/Users/<user>/…` collapsed to `~/…`) instead of the
raw path, and dropped the RTL start-truncation trick whose
bidi reorder flipped leading `/` characters to the end.
2. **Opening `.claude/...` files failed with "forbidden path".**
`@tauri-apps/plugin-fs`'s `readTextFile` enforces the capability
globs declared in tauri.conf.json. Our scope (`$HOME/**`,
`$DOCUMENT/**`, `$DESKTOP/**`, `$DOWNLOAD/**`) legitimately
covers `.claude/...` under $HOME, but the plugin's matcher
denies hidden-directory traversals in practice.
Fix: added a Rust command `read_file_as_text(path)` that uses
`std::fs::read_to_string` directly. Paths reaching this command
are already user-authorised (chosen via native Open dialog or
supplied by the OS through file association), so there's no
additional security value in a scope gate. The platform bridge's
`readTextFile` export now delegates here; no callers change.
Gates:
- npm run typecheck / lint (36 warnings) / format:check: clean
- npm run test:unit: 93/93
- npm run test:e2e: 16/16
- cargo check / clippy -D warnings / test (10/10): clean
Screenshots in Phase 2's plan and in the PR body still match the
updated welcome + viewer look.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…resh Fixes a bug where opening a markdown file from Finder / `open -a` / drag-drop left the Welcome window stuck on its landing state. Root cause: `RunEvent::Opened` synchronously called `open_file_in_window_internal`, whose emit of `viewer://open-path` to the `main` window could land before the React `WindowRouter` listener was registered — the event was silently dropped. New routing (single mechanism, idempotent): - Rust `RunEvent::Opened` + `tauri-plugin-single-instance` callback: push every path into `PendingOpenedFiles` and emit a payload-less `file://open-request` nudge to the app. No more direct `open_file_in_window_internal` call from these handlers. - App.tsx: drain `get_pending_opened_files` on mount AND on every `file://open-request` event. Drain calls `openFileInWindow` per path, which round-trips through Rust's `open_file_in_window` and emits `viewer://open-path` to main — by that point all React effects have fired, listeners are live, and the transition is reliable. Cold-start coverage: drain-on-mount picks up paths stashed before the webview was ready. Hot-open coverage: `file://open-request` nudge triggers a re-drain while JS is fully live. Duplicate coverage: the registry's `lookup_by_path` collapses already-open paths to a focus on the existing window. ### Docs refresh (Phase 8+ cleanup) - README.md — rewritten for the view-only, one-window-per-file architecture: updated feature list, tech stack (Tauri 2.10, React 19, Vite 7), new npm scripts. Contains a `<!-- TODO: refresh screenshots -->` stub. - CHANGELOG.md — new `[Unreleased] - 2026-04-23` entry covering the refactor; the `[0.1.0]` entry is left historical. - CONTRIBUTING.md — rewritten: new dev commands (typecheck, lint, format, test:unit, test:coverage, test:e2e, test:all), per-window architecture pointers (`open_file_in_window`, `window_registry.rs`, `menu://` namespace), testing guidance for Vitest + Playwright web-mode harness + cargo test. - docs/testing.md — rewritten to describe the three-layer test stack, the `__MARKDOC_MOCK__` hooks, fixtures, and the stable `data-testid` list on Welcome and Viewer. - docs/techspecs/editor-sync-scrolling.md — deleted (feature removed). - docs/techspecs/ — deleted (empty). - docs/ai/multi-window-tabs-progress.md — deleted (stale AI scratch log for a superseded feature; `docs/ai/` is otherwise gitignored). Gates: - npm run typecheck / lint (35 warnings) / format:check: clean - npm run test:unit: 93/93 - npm run test:e2e: 16/16 - cargo check / clippy -D warnings / test (10/10): clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The RunEvent::Opened and RunEvent::Reopen match arms are gated on #[cfg(target_os = "macos")]. On Linux / Windows those arms are compiled out, leaving the closure's `app_handle` parameter unused — which tripped `cargo clippy -D warnings` on the GitHub Actions ubuntu runner even though it compiled cleanly on the macOS dev box. Fix: take `&app_handle` unconditionally at the top of the closure so the compiler sees it as used on every platform. Also applied `cargo fmt` to the file. Caught by CI on PR #7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pivots MarkDoc from a multi-tab markdown editor+viewer into a view-only, one-window-per-file reader. Also brings the codebase up to a solid working state with ESLint/Prettier, Vitest, expanded Playwright coverage, and a gated PR CI workflow.
What changed
Product shape
viewer-<N>).mainwindow into viewer mode in-place; subsequent files spawn new windows.CFBundleTypeRole, Windows MSI + WiX description).Architecture
src/windows/{WelcomeWindow, ViewerWindow, WindowRouter}.tsx.open_file_in_window(path)canonicalises paths and routes to existing window / main-window adopt / new window, keyed by aWindowRegistry(src-tauri/src/window_registry.rs).RunEvent::Opened(Finder),tauri-plugin-single-instancecallback, andRunEvent::Reopen(macOS dock click) all funnel through the same routing.localStorage['markdoc-preferences']; recents inlocalStorage['markdoc-recent-files'](legacy string entries transparently upgraded).Tooling & quality
.github/workflows/ci.ymlrunning typecheck/lint/format/unit/e2e/cargo check/clippy/test on every PR.verifygate that blocks the build matrix on a full green test run.Dependencies
@codemirror/*(10 packages),@uiw/react-tabs-draggable, Tailwind toolchain (tailwindcss, @tailwindcss/postcss, autoprefixer, postcss), pluspostcss.config.jsandtailwind.config.js. Tailwind was never actually wired in.npm audit: 0 vulnerabilities.Code deletions
extractRenderedHtmlfrom pdfExport.detached.tsx,detached.html.document.rs(replaced by window_registry), plus all document/tab/edit-mode commands.src/App.tsxshrinks from ~1400 → 116 lines.Test plan
Automated (all green on branch HEAD
5022d98):npm run typecheck— cleannpm run lint— 0 errors, 36 warnings (down from 126 on the legacy codebase)npm run format:check— cleannpm run test:unit— 93/93 Vitest tests pass; coverage 96.66% statements / 90.59% branches; hooks 98.82%, utils 95.57%npm run test:e2e— 16/16 Playwright tests pass (welcome, viewer-open, theme, zoom, sidebar, export, help, autoresize)cargo check --locked/cargo clippy --locked -- -D warnings— cleancargo test --locked— 10/10 WindowRegistry tests pass (register/lookup/release round-trips, canonical path + symlink collapsing, monotonic label allocator)cargo check --locked --release— release profile compilesNative desktop smoke (please verify before merge — handled outside CI):
main)viewer-<N>windowCommits
chore(tooling): add ESLint, Prettier, Vitest, and PR CIrefactor(frontend): per-window viewer + welcome landingrefactor(rust): window registry, routing commands, and view-only menuschore(installer): advertise MarkDoc as a Viewertest: unit + e2e + rust coverage for view-only architecturechore(deps): remove dead deps, bump safe minors, gate release on testsdocs: rewrite CLAUDE.md for view-only multi-window architecture🤖 Generated with Claude Code