From 1fd8acb3e83ec2d4ccf0877247e2571ce8d30e0a Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Mon, 23 Feb 2026 11:10:03 -0600 Subject: [PATCH] docs: update with high-level info and add frontmatter --- docs/architecture.md | 208 +++++++++++++++---------------------------- docs/nlp.md | 76 ++++++++++++++++ docs/pdf.md | 57 ++++++++++++ docs/persistence.md | 38 ++++++++ docs/roadmap.md | 6 +- docs/spec.md | 20 +++-- docs/state.md | 60 +++++++++++++ 7 files changed, 319 insertions(+), 146 deletions(-) create mode 100644 docs/nlp.md create mode 100644 docs/pdf.md create mode 100644 docs/persistence.md create mode 100644 docs/state.md diff --git a/docs/architecture.md b/docs/architecture.md index 34b61b3..317de50 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,177 +1,111 @@ -# Architecture +--- +title: "Architecture" +last_updated: 2026-02-23 +--- -## Foundational Assumptions +## System Overview -- **Canonical storage = OS locations (folders)** + **sidecar SQLite index + JSON settings**. -- Markdown parsing/rendering is **authoritative in Rust** (single source of truth), using a spec-complete CommonMark+GFM engine (recommended: **Comrak**). ([Crates.io][comrak]) -- Frontend uses **Elm architecture** with `Model/Msg/update/Cmd/Sub`. -- Rust communicates with the frontend via **Tauri commands + events**. ([Tauri][tauri-calling-rust]) +Writer is a React + Tauri desktop app with a clear split: -## Constraints +- Frontend (`src/`): UI composition, editor interactions, and client state. +- Backend (`src-tauri/` + Rust crates): filesystem access, indexing/search, markdown rendering, and persisted settings. -1. **React + Tauri** desktop app. -2. **Hand-made Elm architecture** for state management (single source of truth `Model`, message-driven `update`, explicit `Cmd` effects, `Subscriptions`). -3. Storage must follow **three patterns**: - - **Filesystem as canonical store** - - **Sidecar metadata** - - **Sync as transport** -4. Must use **OS "Locations"**: user picks folders via native dialogs; app persists access, and treats those folders as roots for documents. -5. Security posture: Tauri **capabilities/permissions** and filesystem scoping, not "unrestricted arbitrary fs" by default. ([Tauri][tauri-capabilities]) +Canonical content lives in user-selected folders ("locations"). The app database stores derived metadata, search indexes, and app settings. -## Workspace Layout +## Frontend Architecture -- `src/` (React UI) -- `crates/core/` (`writer-core` domain types, markdown API, storage API) -- `crates/markdown/` (`writer-md` markdown engine wrapper around Comrak) -- `crates/store/` (`writer-store` SQLite + FTS) -- `src-tauri/` (`writer` Tauri app + command handlers) -- `fixtures/markdown/` — input markdown + expected HTML + expected outline metadata -- `fixtures/render/` — XSS / raw HTML testcases +### State Layers -## Data Model +- Zustand (`src/state/appStore.ts`) is the primary app store: + - layout/editor presentation state + - workspace state (locations, docs, selection) + - tabs and PDF export status +- Jotai (`src/state/searchAtoms.ts`) is used for localized search UI state. -### Canonical content (user-controlled) +### Command Boundary (`ports`) -- All user documents are **plain-text files** stored *in the user's chosen locations* (e.g., `~/Documents/Writing`, `~/Library/Mobile Documents/...` for iCloud Drive, Dropbox folder, etc.). -- Supported file types (MVP): +`src/ports.ts` defines a typed command model (`Cmd`) and command builders (`locationList`, `docOpen`, `searchDocuments`, `renderMarkdownForPdf`, etc.). - - `.md` (Markdown) - - `.txt` (plain text) - - optional: `.mdx` later (non-MVP) +Runtime flow: -The app **never requires** content to live in an app-private container. +1. UI/hook emits command +2. `runCmd` invokes Tauri command or watcher action +3. result/event is normalized +4. Zustand/Jotai state is updated -### Sidecar metadata (app-controlled) +Hooks such as `useWorkspaceSync`, `useWorkspaceController`, and `useSearchController` orchestrate this flow. -All app-owned derived data lives in the app's application data directory (e.g., `AppData`/`Application Support` equivalent), not inside user content roots. +## Backend Architecture -- `app.db` (SQLite) - - Search index via **FTS** (FTS5 recommended) - - Document catalog (path, file hash/mtime, size, encoding) - - Tag index (derived from frontmatter or inline syntax) - - "Recent documents", pin list, writing sessions stats -- `settings.json` (small, human-readable) - - UI preferences, editor settings, enabled locations list -- `workspace.json` - - Window layout, last open doc, sidebar state, etc. +### App State and Commands -> Tauri provides an official **Store plugin** for persisting small state to a file (async), which is appropriate for `settings/workspace` class data. ([Tauri][tauri-store]) +Tauri manages an `AppState` (`src-tauri/src/commands.rs`) containing: -## Frontend: Elm Loop in React +- `store: Arc` (SQLite-backed domain store) +- `watchers: Mutex>` (per-location filesystem watchers) -Define: +Command surface includes: -- `Model` (single immutable state tree) -- `Msg` (all possible events - TS enum) -- `update(model, msg) -> [model, Cmd[]]` -- `Cmd` (effects): invokes Rust commands, timers, file watchers subscription changes -- `subscriptions(model) -> Sub[]`: - - file watcher events - - debounced autosave ticks - - OS focus/blur, window events +- locations: `location_add_via_dialog`, `location_list`, `location_remove`, `location_validate` +- documents: `doc_list`, `doc_open`, `doc_save`, `doc_exists` +- watchers: `watch_enable`, `watch_disable` +- search/render/settings: `search`, `markdown_render`, `markdown_render_for_pdf`, `ui_layout_get/set`, `style_check_get/set` -**Rule:** UI components are pure views of `Model`. They may *dispatch* `Msg`, but they do not perform I/O. +### Rust Crates -## Core: Rust "Ports" Layer +- `crates/core`: shared domain types and error contracts +- `crates/store`: SQLite schema, location/doc catalog, FTS, settings persistence, atomic save +- `crates/markdown`: markdown rendering for preview and PDF AST extraction -All filesystem and indexing operations happen in Rust commands (Tauri backend), because: +## Storage and Persistence -- Tauri's JS fs APIs are scoped and safe, but a Rust core gives you stronger control over: - - atomic writes - - canonicalization - - watcher integration - - cross-platform path correctness -- Tauri's security model centers on **capabilities and permissioned commands**. ([Tauri][tauri-capabilities]) +### Filesystem (Source of Truth) -### Core Commands +User documents are read/written under selected locations. Saves are atomic by default (`tempfile` + fsync + rename). -- `location_add_via_dialog() -> LocationDescriptor` -- `location_remove(LocationId)` -- `doc_list(LocationId, filter/sort) -> DocMeta[]` -- `doc_open(DocId) -> { text, meta }` -- `doc_save(DocId, text, policy) -> SaveResult` -- `doc_rename/move/delete` -- `index_rebuild(LocationId)` -- `search(query, options) -> SearchHit[]` -- `watch_enable(LocationId)` / `watch_disable(LocationId)` (or auto from subscriptions) +### SQLite (Derived and Settings) -### Command Contract +`crates/store` stores data in `app.db` under the app data directory (`org.stormlightlabs.writer`). -- Standard response envelope: - - `Ok(T)` / `Err(AppError { code, message, context })` -- Error codes: `NotFound`, `PermissionDenied`, `InvalidPath`, `Io`, `Parse`, `Index`, `Conflict` +Key tables: -## Storage Behaviors +- `locations` +- `documents` +- `docs_fts` (FTS5) +- `app_settings` (`ui_layout`, `style_check` JSON blobs) -### Writes must be atomic +### Access and Scope -- Save pipeline: +On startup, Tauri initializes `tauri_plugin_fs`, `tauri_plugin_dialog`, and +`tauri_plugin_persisted_scope` to support scoped filesystem operations and persisted access grants. - 1. write to temp file in same directory (or safe temp under location root) - 2. fsync as available - 3. rename/replace original -- Update index after successful rename/replace. -- If provider conflicts occur (e.g., Dropbox "conflicted copy"), treat as a new file and surface a conflict UI. +## Rendering Pipelines -### File identity & change detection +### Editor Preview -Store in SQLite: +- Frontend requests `markdown_render`. +- Rust returns HTML + metadata. +- Preview uses `data-sourcepos` mappings for editor/preview sync. -- `path` -- `location_id` -- `mtime`, `size` -- `content_hash` (fast hash; compute on open/save and optionally on watcher events) -- `doc_id` stable key = `(location_id, normalized_relative_path)`; if path changes, doc_id changes unless you implement inode/file-id based identity. +### PDF Export -### Encoding & line endings +- Frontend requests `markdown_render_for_pdf`. +- Rust returns a simplified PDF AST (`PdfRenderResult`). +- Frontend renders PDF with `@react-pdf/renderer` and writes bytes via Tauri file APIs. +- Font pipeline attempts custom bundled fonts first, then falls back to built-in fonts when needed. -- Assume UTF-8 by default; detect BOM and handle losslessly. -- Preserve line endings on save unless user opts into normalization. +## Writer NLP Features -## Indexing & Search +Current NLP/writing-assist features are frontend-first: -### Index policy +- Style Check: rule-based pattern matching (Aho-Corasick) over filler/redundancy/cliche dictionaries + custom patterns. +- POS Highlighting: viewport-scoped token tagging via `wink-nlp`. -- SQLite FTS index is **derived**: +Style-check settings are persisted (`style_check_get/set` + SQLite `app_settings`), while POS highlighting is currently session state. - - It can always be rebuilt. - - It must never be required for opening/editing a file. -- Index update triggers: +## Runtime Lifecycle (High Level) - - on save - - on watcher "changed/created/renamed/deleted" - - on periodic reconciliation (e.g., at app start or every N minutes per location) - -### Query features - -- Full-text search with snippet results -- Filters: - - location - - file type - - updated range -- Sorting: - - relevance - - recently modified - - filename - -## Permissions & Security Model (Tauri v2) - -- Use Tauri **capabilities** to enable only: - - dialog open/save - - fs read/write/rename/mkdir/watch (scoped) -- Leverage fs plugin scoping (glob-based) and prevent parent traversal. ([Tauri][tauri-fs]) -- Persist scopes across restarts via **persisted-scope** (explicitly register after fs plugin). ([Tauri][tauri-persisted-scope]) - -## Defaults - -- **Rust is authoritative** for Markdown → HTML, and the UI is a "viewer" of that output. -- Default profile is **GFM-safe**: rich features, but no unsafe raw HTML. ([Crates.io][comrak]) -- Use Comrak **sourcepos** as the backbone for editor↔preview sync; it's explicitly supported in Comrak render options (with known limitations around lists/inlines). ([Docs.rs][comrak-render]) - -[comrak]: https://crates.io/crates/comrak "comrak - crates.io: Rust Package Registry" -[tauri-calling-rust]: https://v2.tauri.app/develop/calling-rust/ "Calling Rust from the Frontend" -[tauri-capabilities]: https://v2.tauri.app/security/capabilities/ "Capabilities" -[tauri-store]: https://v2.tauri.app/plugin/store/ "Store" -[tauri-persisted-scope]: https://v2.tauri.app/plugin/persisted-scope/ "Persisted Scope" -[tauri-fs]: https://v2.tauri.app/reference/javascript/fs/ "@tauri-apps/plugin-fs | Tauri" -[comrak-render]: https://docs.rs/comrak/latest/comrak/options/struct.Render.html "Render in comrak::options - Rust" +1. Tauri boots plugins, opens store, reconciles locations/indexes, and emits startup events. +2. Frontend hydrates persisted UI/style settings and loads locations/documents. +3. Selecting/opening/saving docs goes through typed commands. +4. Watcher events keep the document index and UI views synchronized with external file changes. diff --git a/docs/nlp.md b/docs/nlp.md new file mode 100644 index 0000000..22f9a9c --- /dev/null +++ b/docs/nlp.md @@ -0,0 +1,76 @@ +--- +title: "Writing & NLP" +last_updated: 2026-02-23 +--- + +## Scope + +- The current "grammar checking" system is rule-based style checking, not a full grammar parser/LLM. +- It runs entirely on the frontend for immediate feedback and low latency. +- There are two writer-NLP features today: +- Style Check (pattern flags) +- Parts-of-Speech (POS) highlighting + +## Style Check + +### What It Does + +- Highlights weak phrasing in the editor (fillers, redundancies, clichés) with non-destructive decorations. +- Uses strikethrough styling only; it does not modify document text automatically. +- Supports optional replacement suggestions on matched patterns. + +### How It Works + +- Core extension: `src/editor/style-check.ts` (CodeMirror `ViewPlugin`). +- Dictionaries are loaded from `src/data/style-dictionaries.json`. +- Matching engine is `PatternMatcher` (`src/editor/pattern-matcher.ts`) using Aho-Corasick for efficient multi-pattern scanning. +- Matching characteristics: +- Case-insensitive +- Word-boundary aware to avoid partial-word false positives +- Supports multi-word patterns and overlapping matches +- Scans visible editor ranges (viewport-based), then emits decorations and optional `onMatchesChange` callbacks. + +### Categories and Custom Patterns + +- Built-in categories: `filler`, `redundancy`, `cliche`. +- Users can enable/disable categories and add/remove custom patterns from the Layout Settings panel. +- Custom pattern input is normalized to lowercase before storage (`AddPatternForm`). + +### Editor Integration + +- The extension is attached in `src/components/Editor.tsx` when `styleCheckSettings.enabled` is true. +- Theme classes: +- `.style-filler` (orange) +- `.style-redundancy` (yellow) +- `.style-cliche` (red) +- Focus mode intentionally disables style-check rendering in panel selectors (`src/state/panel-selectors.ts`) via an override settings object. + +### Persistence + +- Style-check settings are persisted through Tauri commands: +- `style_check_get` / `style_check_set` (`src-tauri/src/commands.rs`) +- Stored in SQLite `app_settings` as key `style_check` (`crates/store/src/lib.rs`) +- Frontend hydration/save loop happens in `src/App.tsx` (load on startup, save on setting changes after hydration). + +## POS Highlighting + +### What It Does + +- Colors tokens by grammatical role to support prose analysis (noun/verb/adjective/adverb/conjunction classes). + +### How It Works + +- Implementation: `src/editor/pos-highlighting.ts`. +- Uses `wink-nlp` + `wink-eng-lite-web-model`, loaded lazily on first use. +- Runs over a buffered viewport window for responsiveness, then applies CodeMirror decorations by POS class. + +### Persistence + +- POS toggle is part of app runtime state (`posHighlightingEnabled` in Zustand). +- It is currently not persisted to backend settings. + +## State Boundaries + +- Runtime UI/state source: Zustand (`src/state/appStore.ts`). +- Search state is separate (Jotai) and unrelated to writer NLP. +- Persistence for NLP-related settings currently covers style-check only; POS state remains session-scoped. diff --git a/docs/pdf.md b/docs/pdf.md new file mode 100644 index 0000000..80c8984 --- /dev/null +++ b/docs/pdf.md @@ -0,0 +1,57 @@ +--- +title: "PDF Exporting" +last_updated: 2026-02-23 +--- + +## Tauri + +- PDF generation is split across backend parsing and frontend rendering. +- Backend command: `markdown_render_for_pdf` (`src-tauri/src/commands.rs`). +- The command uses `writer_md::MarkdownEngine::render_for_pdf` (`crates/markdown/src/lib.rs`) + to parse Markdown and return a PDF-oriented AST (`PdfRenderResult`), not raw PDF bytes. +- Returned payload includes: +- `nodes`: normalized document nodes +- `title`: optional title (front matter title if present) +- `word_count`: estimated words +- Supported node types in the current AST: +- `heading`, `paragraph`, `code`, `list`, `blockquote`, `footnote` +- Important constraint: this AST is intentionally simplified for stable rendering + (for example, inline formatting is flattened to text and many advanced Markdown + structures are not represented as dedicated PDF node types yet). + +## React + +- UI entry point is `PdfExportDialog` (`src/components/pdf/ExportDialog/ExportDialog.tsx`), + opened from the toolbar. +- Export flow in `src/App.tsx`: +- Request AST from backend via `renderMarkdownForPdf(...)` (ports command -> Tauri command). +- Pass AST + export options into `usePdfExport()`. +- `usePdfExport` (`src/hooks/usePdfExport.tsx`) renders a React PDF document (`MarkdownPdfDocument`) with `@react-pdf/renderer`, then writes bytes to a user-selected path via Tauri plugins. + +### Rendering Pipeline + +- `MarkdownPdfDocument` (`src/components/pdf/MarkdownPdfDocument.tsx`) maps AST nodes to React PDF primitives (`Document`, `Page`, `Text`, `View`). +- Export options currently exposed in UI: +- page size, orientation, font size, margins, include header, include footer +- Header/footer rendering is optional and controlled by options. +- Body font follows the current editor font family; code blocks use IBM Plex Mono. + +### Font Strategy and Fallback + +- Font registration is handled by `src/pdf/fonts.ts`. +- Primary path uses custom bundled font files from `/public/fonts`. +- If custom font loading/rendering fails, export retries automatically with built-in PDF fonts (`Helvetica` / `Times-Roman` / `Courier`) to maximize success. +- Font errors are wrapped/serialized (`src/pdf/errors.ts`) and logged with context. + +### File Output and State + +- Output is written using: +- `@tauri-apps/plugin-dialog` (`save`) for destination +- `@tauri-apps/plugin-fs` (`writeFile`) for bytes +- Export lifecycle state is tracked in Zustand (`isExportingPdf`, `pdfExportError` in `src/state/appStore.ts`) via `startPdfExport`, `finishPdfExport`, and `failPdfExport`. +- If the user cancels the save dialog, export exits cleanly without writing a file. + +### Persistence Notes + +- PDF files themselves are persisted to the chosen filesystem path. +- Export dialog options are local React state (session-only) and are not currently persisted in app settings. diff --git a/docs/persistence.md b/docs/persistence.md new file mode 100644 index 0000000..1c8be1b --- /dev/null +++ b/docs/persistence.md @@ -0,0 +1,38 @@ +--- +title: "Persistence" +last_updated: 2026-02-23 +--- + + +## Tauri + +- Persistence is backend-owned; the frontend does not use Zustand/Jotai persistence middleware. +- The Tauri app initializes: +- `tauri_plugin_persisted_scope` for restoring previously granted filesystem access. +- `writer_store::Store::open_default()` for durable app data. +- The frontend reads/writes persisted settings through commands (`ui_layout_get/set`, `style_check_get/set`) in `src/App.tsx`. + +### SQLite + +- Database file: `dirs::data_dir()/org.stormlightlabs.writer/app.db` (`crates/store/src/lib.rs`). +- Core tables: +- `locations`: user-added roots (`name`, `root_path`, `added_at`). +- `documents`: indexed metadata per document (`location_id + rel_path` primary key). +- `docs_fts`: FTS5 index for full-text search. +- `app_settings`: JSON blobs for app-level settings (`ui_layout`, `style_check`). +- Lifecycle behavior: + - Startup reconciliation validates locations, reindexes documents, and emits backend events for missing paths. + - Document saves update filesystem content and refresh catalog/FTS entries. + +### File System + +- Source-of-truth document content lives in user-selected folders ("locations"), not in SQLite. +- Adding a location uses a native folder dialog; the path is: + - persisted in `locations` table, + - allowed in Tauri fs scope (`allow_directory`), + - then indexed for metadata/search. +- File writes use atomic save semantics by default (`tempfile` + fsync + rename) to reduce corruption risk. +- File watchers (`watch_enable/disable`) keep the index fresh when files change outside the app. +- Important boundary: + - SQLite stores metadata/settings/indexes. + - The filesystem stores the actual document bytes. diff --git a/docs/roadmap.md b/docs/roadmap.md index 90d33c7..91e7224 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,4 +1,8 @@ -# Roadmap +--- +title: "Roadmap" +last_updated: 2026-02-23 +--- + ## Focus mode enhancements (typewriter scrolling + sentence dimming) diff --git a/docs/spec.md b/docs/spec.md index f092e9f..0d46d5c 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1,4 +1,8 @@ -# Writer spec +--- +title: "Writer spec" +last_updated: 2026-02-23 +--- + ## Intent @@ -27,7 +31,7 @@ Tauri's **dialog plugin** can open file/directory selectors; selected paths are ### 3.3 Platform notes - **macOS App Sandbox / MAS**: persistent access to user-selected folders typically involves **security-scoped bookmarks**; Apple's sandboxing docs explicitly describe using bookmarks that grant access when resolved. ([Apple Developer][6]) - - Practical spec stance: if you plan Mac App Store distribution, design the "Location persistence layer" so it *can* be backed by security-scoped bookmarks; for non-MAS builds, Tauri persisted scope may be sufficient depending on entitlements and packaging. + - Practical spec stance: if you plan Mac App Store distribution, design the "Location persistence layer" so it *can* be backed by security-scoped bookmarks; for non-MAS builds, Tauri persisted scope may be sufficient depending on entitlements and packaging. - **Linux (Flatpak/Snap)**: sandboxed deployments often rely on **XDG portals**; the **Document portal** exposes external files to sandboxed apps via a controlled mount (`/run/user/$UID/doc/…`). ([Flatpak][7]) - **Windows**: sandboxed models (UWP-like) preserve file-picker access using concepts like a **future-access list**; even if you're not UWP, this is a useful conceptual model for "remembering user-granted access." ([Microsoft Learn][8]) @@ -38,19 +42,19 @@ Tauri's **dialog plugin** can open file/directory selectors; selected paths are ### Library (Locations-first) - Sidebar shows: - - Locations (root folders) - - Within each: folders + documents tree (optional), or flat list with filters + - Locations (root folders) + - Within each: folders + documents tree (optional), or flat list with filters - "Add Location…" opens folder picker (directory selection). ([Tauri][9]) ### Editor - Split view - Focus modes: - - typewriter scroll - - distraction-free + - typewriter scroll + - distraction-free - Autosave: - - default on (debounced) - - status indicator: Saved / Saving / Error + - default on (debounced) + - status indicator: Saved / Saving / Error ### Search diff --git a/docs/state.md b/docs/state.md new file mode 100644 index 0000000..e8bddd9 --- /dev/null +++ b/docs/state.md @@ -0,0 +1,60 @@ +--- +title: "State Management" +last_updated: 2026-02-23 +--- + +## React + +### Zustand + +- `src/state/appStore.ts` is the main client state container. +- It is organized as composable slices (layout, editor presentation, view mode, + writer tools, workspace, tabs, PDF export) and merged into one `useAppStore`. +- Domain actions in the store are intentionally cross-slice when needed + (for example, tab actions also update `selectedLocationId` / `selectedDocPath` to keep the workspace in sync). +- Most components consume focused selector hooks (`useLayoutChromeState`, `useTabsState`, etc.) + that use `useShallow` to reduce rerenders. +- `resetAppStore()` exists for deterministic test/app reset behavior. + +### Jotai + +- Jotai is used for search UI state in `src/state/searchAtoms.ts`. +- Atoms hold query/results/loading/filters plus simple derived/reset atoms. +- This state is intentionally separate from Zustand because search interactions + are localized and ephemeral. + +### Elmish/Ports + +- `src/ports.ts` is the frontend/backend boundary and follows an Elmish-style + command model: + - `Cmd` describes work (`Invoke`, `StartWatch`, `StopWatch`, `Batch`, `None`). + - `runCmd()` interprets commands and calls Tauri `invoke`/event APIs. + - Command builders (`locationList`, `docOpen`, `uiLayoutSet`, etc.) keep payload/typing + and error normalization centralized. + - Hooks like `useWorkspaceSync`, `useSearchController`, and `useWorkspaceController` + orchestrate effects by dispatching commands and writing results into store/atoms. + +### Globals + +- A few module-scoped values are used for non-UI implementation details: +- `nextTabId` in `src/state/appStore.ts` generates runtime tab ids and is reset + by `resetAppStore()`. +- Request/version refs in hooks (for example `documentRequestRef` in `useWorkspaceSync`) + prevent stale async responses from overwriting newer state. +- These are not persisted and are intentionally outside reactive state. + +## Tauri + +### State + +- Backend shared state is `AppState` in `src-tauri/src/commands.rs`. +- It contains: + - `store: Arc` (SQLite-backed domain store in `crates/store`) + - `watchers: Mutex>` (active filesystem watchers per location) + +### Commands + +- Tauri commands are the backend API surface (`location_*`, `doc_*`, `search`, `watch_*`, `ui_layout_*`, `style_check_*`, `markdown_*`). +- Startup in `src-tauri/src/lib.rs` initializes plugins + store, manages `AppState`, and runs reconciliation. +- In practice, frontend flow is: + - user action -> command builder (`ports.ts`) -> `runCmd` -> Tauri command -> result/event -> React state update. -- 2.51.2