{"owner":"refactoringhq","repo":"tolaria","hasSkills":true,"totalSkillsCount":7,"totalTokensCount":21210,"categories":["gemini-rule","root-instruction","command-prompt","claude-rule","project-spec"],"hasMcp":true,"mcpConfig":{"mcpServers":{"tolaria":{"command":"npx","args":["-y","@modelcontextprotocol/server-tolaria"]}}},"found":["GEMINI.md","AGENTS.md",".claude/commands/laputa-done.md",".claude/commands/laputa-next-task.md",".claude/commands/create-adr.md","CLAUDE.md","docs/ARCHITECTURE.md"],"skills":{"GEMINI.md":"---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Gemini CLI compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n","AGENTS.md":"# AGENTS.md — Tolaria App\n\n## 1. Development Process\n\n### Mandatory-rule exception protocol\n\nUse an exception only when a required service or analyzer remains unavailable after one retry, or when satisfying the rule is technically impossible without increasing security or data-loss risk. The repository owner is the sole approver. Before proceeding, record the blocked command, evidence, affected files, risk, compensating check, approver, and an expiry of at most 72 hours in the Linear issue or final handoff; review and remove the exception within seven days. A preference, deadline, or failing quality gate is not sufficient exception criteria.\n\n### Task tracking (Linear)\n\n**Linear is the only live queue (cutover 2026-10-15).** Desktop work lives in team Tolaria (`TOL`), project **Tolaria Desktop**; mobile work in **Tolaria Mobile**; web work in project Tolaria Web. Do not create or move Todoist tasks. The old Todoist Tolaria project is legacy (its Draft section is being triaged separately); a completed Todoist card commented \"Moved to Linear: TOL-n\" points to its Linear issue.\n\n**Linear** — workspace Refactoring, team Tolaria, issue key `TOL`.\n\n- Workflow: **Backlog** (not ready) → **Open** (ready for an agent) → **To Rework** (review failed: read the latest review comment and fix only that; pick these **before** Open) → **In Progress** (claimed) → **In Review** (pushed, awaiting Luca's QA) → **Done** / **Canceled** / **Duplicate**.\n- Claim an issue by moving it to In Progress **before** starting work. Never take another worker's In Progress issue; resume your own only when a prior claim comment identifies this worker and worktree. Re-read the issue after claiming and stop on a conflicting claim. Exception: follow the mandatory-rule exception protocol if Linear remains unavailable after one retry.\n- Labels: type `Bug` / `Feature` / `Improvement`; `mac-only` (leave for the Mac mini); `linux-ok`; `agent-blocked` (always with a comment saying why it is stuck and what would unblock it); `dubious` and `contributor-pr` (Backlog, awaiting Luca); Source group `Sentry` / `Canny` / `GitHub`. Exception: an unavailable Linear labeling service requires the same owner-approved exception protocol.\n- Reference the issue ID in commit messages, e.g. `fix: keep image width after restart (TOL-123)`.\n\n### Start working on a task\n\n**Before writing a single line of code:** inspect the configured CodeScene project's current Hotspot and Average Code Health and compare them with `.codescene-thresholds`. Then capture the file-level Code Health score for every existing code file you intend to edit. If the project is already below the threshold, **stop and refactor first** — find the worst files with the MCP, improve them, commit, then start the task. If the gate cannot be restored, stop and obtain explicit repository-owner direction before starting feature work.\n\n- Read task description and all comments fully\n- For To Rework: the ❌ QA failed comment tells you exactly what to fix\n- Check `docs/adr/` for relevant architecture decisions before structural choices\n- Check `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md` for relevant structural information\n- For UI tasks: study app visual language and components first. Prioritize reusing existing components, assets, and variables over recreating them.\n- If working on a Linear issue, add a comment: `🚀 Starting work on this task. [Brief description of approach]`\n\n### Commits & pushes\n\n- Local work may happen on `main`, in detached HEAD worktrees, or in other temporary local states. The production path is still direct-to-main: final verified work is pushed to `origin/main`, with no PR branch flow.\n- Mobile foundation is integrated into main. New mobile work follows the same direct-to-main path; the historical foundation-branch fast lane is not the current delivery workflow.\n- Commit every 20–30 min: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`\n- Pre-commit is a lightweight lint gate only. Pre-push runs the full check suite (build + tests + coverage + core Playwright smoke + CodeScene), preferably on three Chunk sidecar lanes for automatic test/coverage work: frontend lint/build/coverage, Rust coverage, and Playwright smoke. The goal is lower wall-clock time than local hooks while keeping each heavy gate isolated; keep local Playwright mainly for authoring, focused reproduction, or sidecar outages.\n- CircleCI is the authoritative outer-loop CI/CD system. `.circleci/config.yml` owns validation, native cross-platform release builds, GitHub Release publication, and Pages publication.\n- **A task is NOT done until `git push origin main` succeeds.** If the hook blocks: read the error, fix it (clippy, tests, CodeScene, build), commit the fix, push again. If a verified push is impossible, stop and obtain explicit repository-owner direction; do not use `--no-verify` without that recorded approval.\n\n### Mobile coding and release\n\nThe general coding, quality, localization, analytics, credentials and completion rules apply unchanged. Read the mobile sections of `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md`; ADRs 0247, 0263 and 0272 define OTA delivery and channel selection.\n\n- Reuse `apps/mobile/src/components/ui` native primitives and `apps/mobile/src/ui` wrappers, not desktop DOM controls. Keep phone/tablet behavior on the shared controller/repository boundary. Run `pnpm mobile:lint`, `pnpm mobile:typecheck`, `pnpm mobile:test` and focused mobile regressions as well as the normal main push gates; scoped mobile checks do not replace them.\n- Verify affected native behavior separately on iPhone, iPad and Android. Browser checks are not native acceptance. Record task-specific owner delegation of manual QA honestly; it does not waive automated gates. A locked Mac leaves affected checks pending while independent work continues.\n- QA is confined to owned disposable vaults/simulators; existing user apps, containers and vault data stay untouched.\n- CircleCI publishes mobile-related main pushes to **Alpha** after its required gates; desktop-only changes are excluded from mobile publication. Confirm the actual publication receipt, source SHA, runtime and update ID before claiming delivery. Native/configuration/dependency changes that alter the runtime require a compatible signed binary, not an incompatible OTA.\n- Use one external **Tolaria Beta** TestFlight group. Settings defaults to **Stable**, with Alpha opt-in; selected channel and running update identity are distinct. Legacy Alpha-native binaries can launch embedded Alpha before preferences load. Retain runtime matching, safe editor flush/restart and Sentry artifact verification.\n- **Stable promotion remains manual**, authorized explicitly by the owner and using the existing exact-tested-Alpha-artifact republish/approval workflow. Never rebuild from moving main or redirect Stable to moving Alpha. Do not submit Apple compliance declarations or grant new credential permissions without explicit owner authorization.\n\n### Hourly mobile queue routine\n\nKeep the scheduled prompt limited to queue orchestration; this file owns coding and release policy.\n\n1. Reuse the routine's attached worktree and inspect current work. Resume its own identified unfinished claim before taking another issue; keep execution serial under the claim protocol above.\n2. Otherwise paginate **Tolaria Mobile** To Rework, then Open; order by priority (unspecified last), then oldest creation time. Skip others' assigned/delegated work, `agent-blocked` issues and unmet dependencies. Do not pull Backlog or In Review into implementation. Follow the claim protocol above.\n3. Implement the selected issue under this file's rules. On a blocker, record the evidence and required owner action in Linear, preserve a safe checkpoint, and continue independent eligible work without mixing partial changes or extending exceptions.\n4. Put verified implementation In Review with its release record, not self-approved Done. Report meaningful milestones or actionable blockers; stay quiet when the queue or status is unchanged. Resume remaining work on the next hourly run. Keep only one active mobile queue routine.\n\n### TDD (mandatory)\n\nRed → Green → Refactor → Commit. One cycle per commit. For bugs: write failing regression test first, then fix. Exception: pure CSS/layout changes.\n\n**Test quality (Kent Beck's Desiderata):** Isolated · Deterministic · Fast · Behavioral · Structure-insensitive · Specific · Predictive. Fix flaky tests first. Prefer E2E over unit tests for user flows.\n\n### Localization (mandatory for UI copy)\n\nAll user-facing UI labels/copy must live in `src/lib/locales/en.json` and be translated into every target listed in `lara.yaml`. Exception: when the localization service remains unavailable after one retry, use the mandatory-rule exception protocol above and do not release untranslated copy without repository-owner approval. When adding or changing interface copy:\n\n```bash\npnpm l10n:translate\n```\n\nUse `pnpm l10n:translate:force` only when intentionally regenerating existing translations. Commit `src/lib/locales/*.json`, `lara.yaml`/`lara.lock` changes if produced, and verify placeholders/product names stayed intact.\n\n### Product analytics (mandatory for meaningful features)\n\nNew features should emit a PostHog event so we can see whether users actually discover and use them. Skip instrumentation when the change has no meaningful user action or when a dedicated event would create noise; record that reason in the completion comment. If the correct instrumentation remains unclear, obtain repository-owner direction before release. Use clear, stable event names, avoid PII or note content, and include only safe metadata that helps evaluate adoption and failures.\n\nWhen adding or changing a meaningful user-facing feature, include the event name(s) in the Linear completion comment alongside QA, docs, and code health. If intentionally not instrumenting a feature, explain why in the completion comment.\n\n### Code health (mandatory)\n\nPre-push compares **Hotspot Code Health** and **Average Code Health** with the exact numeric floors in `.codescene-thresholds` and fails when either score is lower. Pre-commit is lint-only; CodeScene remains mandatory through the file-level review rules below and the pre-push ratchet gate. Thresholds are a **ratchet** — only go up. When pre-push sees improved remote scores, it updates `.codescene-thresholds`, stages it, and stops so you can commit the new floor with normal verified hooks before pushing again. Exception: adding `// eslint-disable`, `#[allow(...)]`, `as any`, or another bypass requires the mandatory-rule exception protocol and repository-owner approval before the edit.\n\n**Release rule:** CodeScene is a before/after gate, not just a final score. Record the starting CodeScene state before edits and the final state after edits. If touched code gets worse, refactor before committing; if the comparison cannot be produced, stop and obtain explicit repository-owner direction.\n\nDo not edit `.codescene-thresholds` to lower the values. If the gate blocks you, improve the code; any proposed exception requires explicit repository-owner approval recorded before the edit.\n\n**CodeScene access order:** use CodeScene MCP tools if available. If MCP is unavailable, use the installed `cs` CLI for file-level review/delta work, and use the CodeScene API (`CODESCENE_PAT` + `CODESCENE_PROJECT_ID`) for project-wide Hotspot/Average threshold checks from `.codescene-thresholds`.\n\n**Before editing any existing code file:** capture its current file-level CodeScene score. After your edits, re-run the same file-level review and verify the score is higher. If the file already starts at `10.0`, it must remain `10.0`. Exception: if the score cannot be obtained after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before editing.\n\n**New files:** every new **scorable code file** must reach CodeScene score `10.0` before commit. If CodeScene reports `null` / \"no scorable code\" for a new file, it must still have zero CodeScene findings/warnings. If either requirement cannot be met, stop and obtain explicit repository-owner direction.\n\n**Before every commit:** run CodeScene file-level review on every touched or newly created code file and verify the rule above. Then run `mcp__codescene__pre_commit_code_health_safeguard` for the repository and do not commit unless its quality gates pass. **Boy Scout Rule:** every file you touch must leave with a higher score, unless it was already `10.0`, in which case it must stay `10.0`. If an analyzer or gate is unavailable, stop and obtain explicit repository-owner approval for a documented exception.\n\n**Before the final direct-to-main push:** run `mcp__codescene__analyze_change_set` with `base_ref=origin/main`. This is Tolaria's PR-preflight equivalent: every affected file must be improved or stable, and the overall quality gate must pass. Refactor and repeat if any file is degraded. Exception: if the analysis cannot complete after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before pushing.\n\n**If CodeScene gate blocks your push:** use `mcp__codescene__code_health_score` to find the worst file, refactor it, commit, push again. Do not wait for laputa-refactor because it is a background loop rather than a substitute for fixing your own regressions; if no in-scope refactor can restore the gate, stop and obtain explicit repository-owner direction.\n\n### Security scan with Codacy (mandatory)\n\nUse Codacy as a security and static-analysis gate before a task is considered releasable.\n\n- **Two required checks:** Codacy's local CLI/MCP analyzes the uncommitted working tree, while the Codacy dashboard also runs server-side tools after a commit is pushed. These are complementary gates. Report each count with its source; if either check is unavailable, stop and obtain explicit repository-owner approval instead of substituting one for the other.\n- **What counts as new code:** both a newly created code, test, script, or executable configuration file and any added code in an existing file. Scan the entire containing file, not only the added lines, because Codacy tools can report findings on unchanged lines or at file level after a change.\n- **Build the scan manifest:** before each Codacy check, enumerate added and modified tracked paths with `git diff --name-only --diff-filter=ACMR \"$(git merge-base HEAD origin/main)\" --` and untracked paths with `git ls-files --others --exclude-standard`. Classify every code, test, script, and executable configuration path as new or existing, record the manifest in the task evidence, and scan every path individually. Rebuild the manifest immediately before commit so no late-created file is omitted.\n- **Before editing existing code:** run a local file scan and record every finding for each code file you intend to touch. Also record that file's findings on Codacy's currently analyzed `main` commit using `codacy_list_files` to resolve the file and `codacy_get_file_issues` to enumerate all paginated results.\n- **Local file scan:** use `mcp__codacy__codacy_cli_analyze` with the repository's absolute `rootPath` and an explicit `file`, one touched/new file at a time. If the MCP is unavailable or fails, run `LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 .codacy/cli.sh analyze <path> --format sarif --output /tmp/codacy-<slug>.sarif`. Omit `--tool` so every locally configured analyzer runs. Explicitly inspect every `runs[].results[]` entry in the SARIF output.\n- **Analyzer-parity gate:** before creating or modifying code, call `codacy_list_repository_tools` and record the enabled dashboard analyzer name, UUID/generation, configuration mode, and language coverage. Compare that roster with the `runs[].tool.driver` entries produced locally for every language in the scan manifest. Tool names alone are not sufficient: deprecated ESLint/ESLint8 and current ESLint9 are different analyzers, and a local run of Lizard, Opengrep, and Trivy does not prove that dashboard ESLint will be clean. For TypeScript/JavaScript, the passing roster must include the current analyzer that honors `eslint.config.js`, plus every other enabled relevant dashboard tool. If the intended analyzer is disabled, a deprecated analyzer is enabled, or any enabled dashboard analyzer has no equivalent successful local run, analyzer parity fails: stop, correct the Codacy tool configuration, trigger a full reanalysis, and verify parity before code work. A repository-owner-approved exception is required if configuration access is unavailable.\n- **Approved analyzer-parity exception (Biome):** Biome is enabled on the Codacy dashboard but missing from the local Codacy CLI. Biome: covered locally by the repo's own Biome (`npx biome check <changed files>`, or the repo's existing Biome run in `pnpm codacy:gate`); approved by Luca 2026-10-10. This is a standing exception recorded here, so it needs no per-task re-approval: record that `npx biome check` run as the local Biome row in the parity and per-file evidence. It covers only the missing local Biome run; any Biome diagnostic on changed files still fails the gate, and every other analyzer must still match.\n- **Per-file analyzer evidence:** for every new code file, record one result row per expected analyzer with the exact file path, analyzer identity, completion status, and finding counts split into Critical/High/Medium/Minor/Info/unclassified. Missing rows, a skipped/untracked target, an analyzer crash, or an analyzer that silently omits the language is a failed scan, never a zero. If the CLI requires Git discovery, add the file to the index normally before the final scan; do not rely on a directory scan to discover an untracked file.\n- **Fail closed on partial scans:** a zero process exit code is not enough. Confirm the expected relevant tools produced SARIF runs and inspect scan logs for crashes, encoding failures, skipped target files, partial analysis, or missing tool output. If any relevant analyzer did not complete, the scan did not pass.\n- **Approved analyzer-coverage exception (unsupported or excluded files):** if Codacy or CodeScene does not support a file type, or excludes the path from analysis (for example test paths, `.husky/*` hook scripts), an agent does not need a pre-edit baseline or a post-edit score from that tool to edit the file. Record \"not analyzed by <tool>: <unsupported|excluded>\" as that tool's row in the parity/per-file evidence. Likewise, when an enabled dashboard analyzer has no local equivalent for a file type (for example Jackson Linter for `src/lib/locales/*.json`), cover it locally with the repo's own check (`node scripts/validate-locales.mjs` plus a JSON parse for locale catalogs) and record that run. Approved by Luca 2026-10-13; standing, no per-task re-approval. Every other gate still applies: the other tool's baseline and Boy Scout rule where it does analyze the file, zero new findings, tests, coverage, pre-push hooks and post-push verification.\n- **Zero-new-findings rule:** added or modified code must introduce no Codacy finding at any severity, including Info/Minor, Warning/Medium, High/Error/Critical, unclassified findings, and findings reported on unchanged lines or at file level because of the change. If this cannot be achieved, stop and obtain explicit repository-owner approval for a documented exception.\n- **New code — absolute zero rule:** every new code file and every file containing newly added code must be scanned explicitly after creation or modification and must have exactly zero findings attributable to the new code at every severity. A newly created file must have exactly zero findings in the whole file. For added code in an existing file, any finding on an added line, caused by an added symbol or dependency, or newly appearing at file level fails the gate. `0 Critical + 0 High + 0 Medium + 0 Minor + 0 Info + 0 unclassified` is the only passing result. If a scanner cannot prove this, stop and obtain explicit repository-owner approval before proceeding.\n- **Boy Scout Rule for existing files:** after editing, every touched file must have fewer local findings than its recorded baseline; a zero-finding file must stay at zero. Fix every existing Critical/High finding in a touched file; if this cannot be achieved, stop and obtain explicit repository-owner approval for the documented exception.\n- **Before every commit:** re-scan every touched/new code file individually and compare the complete SARIF results with its recorded baseline. Do not rely on added-line filtering, repository totals, or `pnpm codacy:gate` alone; if an individual scan cannot complete, stop and obtain explicit repository-owner approval.\n- **Post-push dashboard verification:** after `git push origin main`, wait until `codacy_get_repository_with_analysis` reports the exact pushed SHA as `lastAnalysedCommit`. Re-read the enabled-tool roster, paginate `codacy_list_repository_issues` to completion, and filter by each exact manifest path; use file-level MCP queries as supporting detail, not as a substitute for the complete repository issue list because newly created files may not yet resolve through file lookup. Absence from the issue list counts as zero only when the exact SHA is analyzed, the analyzer roster still matches the pre-edit parity record, and the repository reports no analyzer problem. Every new code file must have zero dashboard findings at every severity from every enabled analyzer; every existing touched file must have no new finding and must have fewer findings than its pre-edit dashboard baseline (or remain at zero). If the dashboard reports a finding—even one from a deprecated or misconfigured analyzer—the task remains unfinished until the file is fixed or the analyzer configuration is corrected and a replacement exact SHA is fully reanalyzed. Exception: if verification remains unavailable after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- **Repository-wide dashboard counts:** use `codacy_get_repository_with_analysis` for the total and `codacy_list_repository_issues` with full pagination for severity/tool breakdowns. Derive the provider/organization/repository from the Git remote without printing credential-bearing remote URLs; for this repository use `gh` / `refactoringhq` / `tolaria` and branch `main`.\n- **Escalation:** if a scanner is unavailable or a finding is demonstrably false, stop and obtain explicit repository-owner approval recorded in the completion comment. Rule suppression requires that same explicit approval and documentation.\n- `pnpm codacy:gate` is a required fail-closed added-line safety net in pre-push and CI; it does not replace the touched-file before/after check.\n\n### Check suite (runs on every push)\n```bash\npnpm lint && npx tsc --noEmit && pnpm test && pnpm test:coverage  # frontend ≥71%\ncargo test && cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85\n```\n\nCoverage is a release gate, not a vanity metric:\n- Frontend coverage must stay ≥71%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- Rust line coverage must stay ≥85%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- For bug fixes, add a regression test when practical.\n- For new behavior, add targeted coverage close to the changed code; do not rely only on broad E2E coverage.\n\n### UI and native QA\n\n**Phase 1 — Playwright (only for core user flows):**\n\nWrite Playwright test in `tests/smoke/<slug>.spec.ts` when the feature touches vault open, note create/save/delete, search, wikilink navigation, git commit/push, or conflict resolution. Tag a test with `@smoke` when it protects one of those core pre-push workflows; keep cosmetic or mock-heavy checks in the full regression lane. Prefer `.chunk/run-playwright-smoke.sh` on a Chunk sidecar for the curated smoke lane because local Playwright is expensive; keep `pnpm playwright:smoke` available for focused local reproduction. The curated smoke suite must stay under **5 minutes** when sharded on sidecars. Exception: if a platform outage prevents the smoke test from running after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n\n```bash\npnpm dev --port 5201 &\nsleep 3\nBASE_URL=\"http://localhost:5201\" npx playwright test tests/smoke/<slug>.spec.ts\n```\n\n**Phase 2 — Native app QA (macOS):**\n\n> On a Linux agent box, skip this Phase 2 block and follow **Linux agent boxes (Hetzner / remote)** below instead.\n\n```bash\npnpm tauri dev &\nsleep 10\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh laputa\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/qa-native.png\n```\n\nUse computer-use/browser-control interaction for native UI QA when either tool is present in the current environment: click, hover, drag, select, scroll, and type the way a real user would with the mouse and trackpad. For every UI feature, test the primary mouse-driven path first, then verify each keyboard shortcut or keyboard-first workflow implemented or modified by the task. If neither interaction tool is present, run the scripted focus, screenshot, and shortcut checks and document that limitation.\n\nUse `osascript` for app focus, keyboard shortcuts, and keyboard-specific checks. **⚠️ WKWebView:** if an `osascript keystroke` fails to enter editor text, use computer use for the native editor interaction and rely on Playwright for deterministic text-input coverage. Write the result as a Linear comment (✅ or ❌), or in the final handoff when no Linear issue exists.\n\n### Linux agent boxes (Hetzner / remote)\n\nThese rules apply when the agent runs on a Linux box (no macOS desktop, no `osascript`, no `~/.openclaw`). The macOS instructions above stay in force for the Mac mini; do not remove or weaken them.\n\n**Mac-native tasks stay with the Mac mini.** Do not claim tasks labeled `mac-only`, or tasks that clearly need macOS/Windows-native surfaces: updater / auto-update, Homebrew, macOS menus and Cmd/Option shortcut wiring, native file/open/save dialogs, signing/notarization, Windows installers. Leave them in the queue for the Mac mini.\n\n**QA on Linux = the Linux Tolaria build with a throwaway vault.** There is no macOS native QA on these boxes. Build the Linux app (`CI=true pnpm tauri build --debug --no-bundle`) and drive it under Xvfb with a throwaway copy of `demo-vault-v2/` — on the agent boxes use the installed `tolaria-qa` CLI (`tolaria-qa help`: `start`, `click`, `key ctrl+…`, `xtype`, `shot`, `smoke`, `stop`). Never open, create, or modify `~/Laputa` or any real user vault. Test the primary mouse path first, then each keyboard shortcut you added or changed (`Ctrl` on Linux where macOS uses `Cmd`). Phase 1 Playwright still applies. Record QA (✅/❌, what was clicked/typed, screenshots) in the task comment as usual.\n\n**Before pushing to main:** the full check suite passes; then `git fetch origin && git rebase origin/main` (stop on conflicts you cannot resolve safely), re-run the checks (including the Linux build and a Linux app smoke) on the rebased HEAD, then `git push origin main` so the pre-push hook runs against the latest `origin/main`. Never `--no-verify` without a recorded repository-owner exception.\n\n### Release-readiness checklist\n\nBefore pushing or moving a task to In Review, verify the release gates and add a **completion comment** to the Linear issue. Exception: when the work has no Linear issue, put the identical evidence in the final handoff and identify it as the release record. The record must include:\n\n- What was implemented (a few lines covering logic and UX/UI).\n- QA: what was tested and how (Playwright / native screenshot / osascript on macOS; Linux app driven with a throwaway vault on Linux agent boxes).\n- Tests/coverage: commands run and final coverage result.\n- CodeScene: before/after touched-file checks, the pre-commit safeguard verdict, the final `origin/main` change-set verdict, plus final Hotspot and Average scores after push; every gate must pass `.codescene-thresholds`, or the record must include explicit repository-owner approval for a documented exception.\n- Coverage commands passed (`pnpm test:coverage` and `cargo llvm-cov ... --fail-under-lines 85`) or the change is docs-only.\n- Codacy: the final scan manifest; local before/after findings for every touched file; confirmation that every relevant local analyzer completed; and post-push dashboard findings after Codacy analyzed the exact pushed SHA. Confirm fewer findings in existing files (or zero stayed zero), exactly zero local and dashboard findings in every new code file, and zero findings attributable to added code in existing files at every severity.\n- Localization: any user-facing copy lives in `src/lib/locales/en.json`, `pnpm l10n:translate` was run, and `pnpm l10n:validate` passes. If no copy changed, say “Localization: no UI copy changes”.\n- PostHog: meaningful new user actions/events are instrumented with safe metadata; noisy/minor changes explicitly say “PostHog: no event needed because …”.\n- Refactoring: any files refactored to meet the CodeScene gate, or \"none needed\".\n- ADRs: any new/updated ADRs, or \"none\".\n- Docs: any updated docs (`docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, etc.), or \"none\".\n- Demo vault dirt checked: `git status --short -- demo-vault demo-vault-v2` is empty unless fixture changes are intentional.\n\n### ADRs & docs\n\nADRs live in `docs/adr/`. Create one in the same commit as the code. Preserve existing ADRs by creating a new one that supersedes them; editing an existing ADR requires an explicit repository-owner request. Use `/create-adr`. **When:** new dependency, storage strategy, platform target, core abstraction, cross-cutting pattern. **Not for:** bug fixes, styling, refactors.\n\nAfter any Tauri command, new component/hook, data model change, or new integration: update `docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, and/or `docs/GETTING-STARTED.md` in the same commit.\n\n---\n\n## 2. Product Rules\n\n### Demo vault hygiene (`demo-vault/`, `demo-vault-v2/`)\n\nDefault to `demo-vault-v2/` for testing.\n\n- Treat `demo-vault/` and `demo-vault-v2/` as disposable QA fixtures unless the task explicitly changes demo content.\n- If you create untracked notes, attachments, or other temporary files there for testing, delete them before the task is complete.\n- If you modify tracked demo-vault files only to test or QA behavior, revert those edits before the final commit.\n- Before declaring a task done, make sure `git status --short -- demo-vault demo-vault-v2` is empty unless demo fixture changes are part of the task.\n- If a fresh run starts and the only local dirt is inside `demo-vault/` or `demo-vault-v2/`, clean those paths first and continue. That case is recoverable QA residue, not a blocker.\n\n### User vault (`~/Laputa/`)\n\nDefault to `demo-vault-v2/`. If you must use `~/Laputa/` for testing:\n- Do not commit or push test notes to the remote vault unless the repository owner explicitly identifies that content as an intended deliverable.\n- **Delete all test notes from disk** when done — do not leave untitled or temporary notes on the filesystem. Run `cd ~/Laputa && git checkout -- . && git clean -fd` to restore the vault to its last committed state.\n- **Rationale:** test notes pollute the local vault over time, making it a collection of nonsensical untitled files. Keep the vault clean on disk as well as on the remote; any intended fixture exception requires explicit repository-owner approval.\n\n### UI components — mandatory rules\n\nUse shadcn/ui components for user-facing interactive elements instead of raw HTML form elements (`<input>`, `<select>`, `<button>`, native `<input type=\"date\">`, etc.). If no suitable shadcn or existing app component exists, stop and obtain explicit repository-owner design approval before introducing a raw control.\n\n| Need | Use |\n|---|---|\n| Text input | `Input` from shadcn/ui |\n| Dropdown/select | `Select` from shadcn/ui |\n| Date picker | `Calendar` + `Popover` from shadcn/ui (NOT native `<input type=\"date\">`) |\n| Button | `Button` from shadcn/ui |\n| Autocomplete/combobox | Reuse existing combobox components from the app (check `src/components/`) |\n| Wikilink picker | Reuse the wikilink autocomplete component already used in the editor and Properties panel |\n| Emoji picker | Reuse the emoji picker component already used for note/type icons |\n| Color picker | Reuse the color swatch picker used for type customization |\n| Toggle/switch | `Switch` or `ToggleGroup` from shadcn/ui |\n| Dialog/modal | `Dialog` from shadcn/ui |\n\n**Component search trigger:** when the requested interaction does not map to a component in the table above, search `src/components/` by the interaction name and ARIA role before building a component. **Visual language:** new UI must follow Tolaria's existing components and design tokens; a deliberate exception requires explicit repository-owner design approval recorded through the mandatory-rule exception protocol.\n\n---\n\n## 3. Reference\n\n### Release notes\n\nRelease notes live in `release-notes/<tag>.md` and are published to GitHub Releases and the public website.\n\n**Format:**\n- Every release note must start with frontmatter; exceptions require the mandatory-rule exception protocol:\n  ```markdown\n  ---\n  type: Note\n  _organized: true\n  ---\n  ```\n- After frontmatter, write the user-facing content (features, improvements, fixes)\n- Use clear headings: `## New Features`, `## Improvements`, `## Stability and Fixes`\n- Use emojis and **bold** feature names for scanability\n- Write for end users, not developers\n\n**Frontmatter handling:**\n- The vault displays release notes as organized `Note` types\n- GitHub Release bodies and website copies automatically strip frontmatter via `scripts/strip-frontmatter.mjs`\n- Never expose frontmatter in user-facing surfaces\n\n**Creating a new release note:**\n```bash\ncat > release-notes/<tag>.md << 'EOF'\n---\ntype: Note\n_organized: true\n---\n\n## New Features\n\n- 🎉 **Feature name** — Description.\n\n## Improvements\n\n- ✨ **Enhancement** — Description.\nEOF\n```\n\nThe frontmatter ensures release notes are already organized when the Tolaria repo is opened as a vault, avoiding inbox clutter.\n\n### macOS / Tauri gotchas\n\n- `Option+N` → special chars on macOS. Use `e.code` or `Cmd+N`\n- Tauri menu accelerators: `MenuItemBuilder::new(label).accelerator(\"CmdOrCtrl+1\")`\n- `app.set_menu()` replaces the ENTIRE menu bar — include all submenus\n- `mock-tauri.ts` silently swallows Tauri calls — not a substitute for native testing\n\n### QA scripts\n\n```bash\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh Tolaria\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/out.png\nbash ~/.openclaw/skills/tolaria-qa/scripts/shortcut.sh \"command\" \"s\"\n```\n\n### Diagrams\n\nPrefer Mermaid (`flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram-v2`). ASCII only for spatial wireframe layouts.\n\n## Stuck or blocked tasks\n\nWhenever an agent sets the `agent-blocked` label, moves a task back to Open, or otherwise leaves a task stuck, it must ALWAYS add a task comment explaining why: the exact blocker, the evidence (command/log path), and what decision or action would unblock it. Never leave a task blocked without that comment. Exception: a Linear outage after one retry requires the identical evidence in the final handoff and repository-owner approval under the mandatory-rule exception protocol.\n",".claude/commands/laputa-done.md":"# /laputa-done <task_id>\n\nMark a Laputa task as done: add completion comment, move to In Review, then self-dispatch the next task.\n\nRun this after Phase 1 (Playwright) and Phase 2 (native app QA) both pass **and `git push origin main` has succeeded**.\n\n⚠️ A task is NOT done until the push succeeds. If the push is blocked by the pre-push hook (clippy, tests, CodeScene, build):\n- Read the error\n- Fix it (never use `--no-verify`)\n- Commit the fix and push again\n- Repeat until push exits with code 0\n\n## Steps\n\n**1. Add completion comment to the task**\n\nSummarize what was done — this is the context Luca and Brian will read in Todoist:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"task_id\": \"$ARGUMENTS\",\n    \"content\": \"✅ Implementation complete.\\n\\n**What changed:** [brief summary of the implementation]\\n**ADR:** [if an ADR was created, reference it here; otherwise omit]\\n**Playwright:** all tests pass\\n**Native QA:** tested with pnpm tauri dev — [describe what was tested and what was observed]\"\n  }'\n```\n\n**2. Move task to In Review**\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/$ARGUMENTS/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjX33FF4Vj86M\"}'\n```\n\n**3. Pick the next task**\n\nRun `/laputa-next-task` to get the next task and start working on it immediately.\n\nIf there are no tasks, `/laputa-next-task` will wait 10 minutes and retry automatically. Do NOT exit — stay alive and let it loop.\n",".claude/commands/laputa-next-task.md":"# /laputa-next-task\n\nPick the next Laputa task from Todoist and move it to In Progress.\n\nPriority order: **To Rework** first, then **Open** (sorted by Todoist priority p1→p4).\n\n## Steps\n\n1. Fetch tasks from To Rework (`6g6QqvR9rRpvJWvv`), then Open (`6g3XjWR832hVHhCM`)\n2. **Sort by priority — this is mandatory.** Todoist returns tasks in arbitrary order. You must sort them yourself:\n   - Todoist priority field: `4` = p1 (urgent), `3` = p2, `2` = p3, `1` = p4\n   - Sort descending by `priority` field (4 first, 1 last)\n   - To Rework tasks always come before Open tasks regardless of priority\n   - **Never pick a p3/p4 task if a p1/p2 task exists in the same section**\n3. Take the first task from the sorted list\n4. Move it to In Progress (`6g3XjWjfmJFcGgHM`) via Todoist API:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/<task_id>/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjWjfmJFcGgHM\"}'\n```\n\n5. Add a \"started\" comment to the task:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"task_id\": \"<task_id>\", \"content\": \"🚀 Starting work. [Brief description of approach or what needs to be fixed]\"}'\n```\n\n6. Fetch the full task details (description, comments) from Todoist:\n\n```bash\ncurl -s \"https://api.todoist.com/api/v1/tasks/<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n\ncurl -s \"https://api.todoist.com/api/v1/comments?task_id=<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n```\n\n6. For To Rework tasks: read the ❌ QA failed comment — it tells you exactly what to fix\n7. Output: task ID, title, and full description so you can start working immediately\n\nIf no tasks are available in either section → wait 10 minutes and try again (loop forever):\n\n```bash\nwhile true; do\n  # ... check tasks ...\n  if no_tasks; then\n    sleep 600  # 10 minutes\n  else\n    break  # got a task, proceed\n  fi\ndone\n```\n\nDo NOT exit when there are no tasks. Keep looping until a task appears. This keeps Claude Code alive permanently — the watchdog is a safety net only, not the primary dispatcher.\n",".claude/commands/create-adr.md":"# Create Architecture Decision Record\n\nUse this command when you need to document an architectural decision made during a task.\n\nInspired by [adr-tools](https://github.com/npryce/adr-tools) (Nygard format), adapted for Laputa's frontmatter-based note format.\n\n## When to use this\n\nCreate an ADR when your work involves any of these:\n- Choosing a storage strategy (vault vs app settings vs database)\n- Adding or removing a major dependency\n- Supporting a new platform or target\n- Introducing or removing a core abstraction\n- Making a cross-cutting decision that affects how future code should be written\n\nDo NOT create ADRs for: bug fixes, UI styling, refactors that preserve behavior, or test additions.\n\n## Creating a new ADR\n\n### 1. Find the next ID\n\n```bash\nls docs/adr/*.md | grep -oP '\\d{4}' | sort -n | tail -1 | xargs -I{} printf '%04d\\n' $(({} + 1))\n```\n\nIf no files exist yet, start at `0001`.\n\n### 2. Create the file\n\nFilename: `docs/adr/NNNN-short-kebab-title.md`\n\nTemplate:\n\n```markdown\n---\ntype: ADR\nid: \"NNNN\"\ntitle: \"Short decision title\"\nstatus: active\ndate: YYYY-MM-DD\n---\n\n## Context\n\nThe issue motivating this decision, and any context that influences or constrains it.\n\n## Decision\n\n**The change we're proposing or have agreed to implement.** State it clearly in one or two sentences — bold so it stands out.\n\n## Options considered\n\n- **Option A** (chosen): brief description — pros / cons\n- **Option B**: brief description — pros / cons\n- **Option C**: brief description — pros / cons\n\n## Consequences\n\nWhat becomes easier or harder as a result?\nWhat risks does this introduce that will need to be mitigated?\nWhat would trigger re-evaluation of this decision?\n\n## Advice\n\n*(optional)* Input received before making this decision — who was consulted, what they said.\nOmit this section if the decision was made without external input.\n```\n\n### 3. Update the index\n\nAdd a row to `docs/adr/README.md`:\n\n```markdown\n| [NNNN](NNNN-short-kebab-title.md) | Title | active |\n```\n\n### 4. Include in the same commit as the feature\n\n```bash\ngit add docs/adr/NNNN-*.md docs/adr/README.md\n# fold into the feature commit — do not create a separate commit just for the ADR\n```\n\n---\n\n## Superseding an existing ADR\n\nEquivalent of `adr new -s <N>` from adr-tools — do this in two steps:\n\n### Step 1: Mark the old ADR as superseded\n\nEdit the existing file — add `superseded_by` and update `status`:\n\n```yaml\n---\ntype: ADR\nid: \"000N\"\ntitle: \"Old decision title\"\nstatus: superseded          # ← change from active\nsuperseded_by: \"NNNN\"       # ← add this\ndate: YYYY-MM-DD\n---\n```\n\n**Never edit the content sections** of an active ADR — only the status metadata.\n\n### Step 2: Create the new ADR\n\nFollow the steps above. In the **Context** section, reference the superseded ADR:\n\n```markdown\n## Context\n\nSupersedes [ADR-000N](000N-old-title.md).\n\n[explain why the old decision no longer holds]\n```\n\n### Step 3: Update the README index\n\nChange the old row's status to `superseded`, add the new row.\n\n---\n\n## Best practices (from adr-tools / Nygard)\n\n- **One decision per ADR** — if you find yourself writing \"and also\", split it\n- **Write Decision first** — if you can't state it in 1-2 sentences, the decision is too vague\n- **Context is the \"why now\"** — what forced this decision to be made today?\n- **Consequences should include negatives** — a one-sided ADR is a red flag\n- **Committed = immutable** — once pushed, the content doesn't change; only status metadata does\n- **If in doubt, create one** — cheaper to have an unnecessary ADR than to lose context\n- Date = today's date, `YYYY-MM-DD`\n","CLAUDE.md":"---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Claude Code compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n\n<!-- verity-memory:start -->\n## Project Memory\n\nThis project has a knowledge graph maintained at `.verity/memory/`. Before starting\nnon-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns\nthat may apply to the change you are about to make. Open specific node files via\nthe Read tool when the title or scope suggests relevance.\n\nThe graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are\nsuperseded — ignore them unless investigating history.\n\nKnowledge the organization's other repositories learned is mirrored outside the\nrepo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).\nEach claim says where it came from; one that is wrong here is demoted for\neveryone with `verity memory demote <id> --reason \"…\"`.\n\n## Quality gate: accepted risks\n\nWhen the Verity pre-commit/pre-push gate FAILs, fix the findings — that is the\ndefault. Use `verity waive <pattern-id> --file <path> --reason \"…\"` ONLY to relay\na risk a human has explicitly accepted: a named code-review finding, an ADR, or\nthe user saying so in this conversation. The --reason must cite that source.\n\nNever waive on your own judgment, to get past a block, or pre-emptively. A waive\nbinds to the file's current bytes and voids automatically when the file changes,\nand every waive is recorded in the run ledger. For a pattern-level false positive\nuse `verity feedback finding <run-id> <pattern-id> false_positive` instead.\n\n## Post-task reflection\n\nWhen a task is complete (you've created a PR, the user says \"done\" or \"ship it\",\nor the work is clearly finished), **draft the reflection yourself** — 1–3\nconcrete things worth remembering (a decision, a gotcha, or a pattern), each\ncited with the files / PR / commands / error-signatures it came from. Skip\nentirely if nothing non-obvious happened — that judgement is the ONLY filter,\nbecause nothing reviews the reflection before it lands.\n\nThen record it straight away. There is no confirm step, in any environment:\n\n```bash\nverity reflect --user-input - --kind <kind> <<'VERITY_EOF_<random>'\n<your draft>\nVERITY_EOF_<random>\n```\n\nThe draft goes through the quoted heredoc, never inside `\"…\"` on the command\nline, where a `\"` or `$(…)` in it would run in the shell. Replace `<random>`\nwith 8 random letters and digits that you pick for this call, the same at both\nends. Text written before you picked them cannot end the heredoc early.\n\nAdd `--confirmed` ONLY when the user authored or dictated the words. Without\nit the node is stored as `source: agent` — Verity thought this, nobody checked\nit. With it, `source: user` at full confidence — a person stands behind this.\nNever claim the second for your own draft, however good it is.\n\n**Name the files in the text.** Verity scopes the reflection to the paths it\ncites, and a reflection that names no file in this repo is never retrieved for\na later review — it is recorded and then invisible. The command says so when it\nhappens; `--file-globs '<path or glob>'` is the fix when the prose cannot carry\nthe paths.\n\nThen tell the user, in one line, what you recorded and where: the command\nprints the node id, the path under `.verity/memory/`, and a dashboard link.\nThey did not agree to it in advance, so say it happened — editing or deleting\nthat file is how they correct it.\n\n> Durable, hand-curated guidance goes in the preserve region below (it survives\n> regeneration) or anywhere OUTSIDE these markers. Everything else between the\n> markers is tool-owned and overwritten on each run.\n\n## Housekeeping Turns\n\nWhen a turn will be pure housekeeping — pulling, installing dependencies,\nrebasing, a formatting sweep you are not authoring — declare it BEFORE doing it:\n\n```bash\nverity ignore --turn --agent --reason \"pulling latest before starting\"\n```\n\nThis skips the review for that turn, which saves the turn Verity would\notherwise spend saying it had nothing to say. Use `--for 30m` instead of\n`--turn` when a single piece of housekeeping spans several turns.\n\n**It is a claim about the turn, not a way to silence review.** The declaration\nis checked against what the turn actually did: if anything is authored — by you,\nby a subagent, or by a shell command that can write files — it voids, the review\nruns anyway, and the broken declaration is reported. So declare housekeeping you\nare about to do, never work you have already done, and never as a way to get past\na finding. Declarations are budgeted per session and every one is recorded with\nits reason.\n\n<!-- verity-memory:preserve -->\n<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. -->\nVerity is set up only on some machines (currently the agent-claude box). If the `verity` CLI or\n`.verity/memory/` is missing, skip the Verity steps above; `AGENTS.md` stays the source of truth.\n<!-- /verity-memory:preserve -->\n<!-- verity-memory:end -->\n","docs/ARCHITECTURE.md":"# Architecture\n\n## Desktop Note Save Transport\n\n`persistContent` sends native desktop saves through `save_note_content_bytes`\nfor notes over 256 Ki characters, using raw UTF-8 content and a base64 UTF-8 `x-tolaria-note-path` header. The\ncommand rejects malformed requests and delegates to `save_note_content`, retaining\nactive-vault path validation and the existing off-thread atomic writer. This\nbounds the JSON note payload and avoids a full JSON copy of embedded image payloads; memory remains proportional\nto note length. Browser fixtures retain their JSON save API. See ADR 0278.\n\nTolaria is a personal knowledge and life management desktop app. It reads a vault of markdown files with YAML frontmatter and presents them in a four-panel UI inspired by Bear Notes.\n\n## Design Principles\n\n### Filesystem as the single source of truth\n\nThe vault is a folder of plain markdown files. The app never owns the data — it only reads and writes files. The cache, React state, and any in-memory representation are always derived from the filesystem and must be reconstructible by deleting them. When in doubt, the file on disk wins.\n\n### Convention over configuration\n\nTolaria is opinionated. Standard field names (`type:`, `status:`, `url:`, `Workspace:`, `belongs_to:`, `related_to:`, `has:`, `start_date:`, `end_date:`) have well-defined meanings and trigger specific UI behavior — without any setup. Relationship defaults are stored in snake_case on disk and humanized in the UI. This is not convention *instead of* configuration: users can override defaults via config files in their vault (e.g. `config/relations.md`, `config/semantic-properties.md`). But the defaults work out of the box, and most users never need to touch them.\n\nThis principle directly serves AI-readability: the more structure comes from shared conventions rather than per-user custom configurations, the easier it is for an AI agent to understand and navigate the vault correctly — without needing bespoke instructions for every setup.\n\n### Where to store state: vault vs. app settings\n\nWhen deciding where to persist a piece of data, ask: **\"Would the user want this to follow them across all their Tolaria installations — other devices, future platforms (tablet, web)?\"**\n\n| Follows the vault | Stays with the installation |\n|-------------------|-----------------------------|\n| Type icon, type color | Editor zoom level |\n| Pinned properties per type | API keys (OpenAI, Google) |\n| Sidebar label overrides | Auto-sync interval |\n| Property display order | Window size / position |\n| Per-note `_width` rich-editor width override | Default rich-editor note width |\n| Applicable vault or parent-repository `.gitignore` patterns | Whether this installation hides Gitignored files |\n| N/A | Whether this installation shows Git features |\n| N/A | Git executable provider (`native` vs WSL2 distribution) |\n| Per-vault All Notes note-list column overrides | All Notes PDF/image/unsupported file visibility |\n| N/A | Per-vault Git setup prompt opt-out |\n| Type `_sidebar_label` overrides | Whether this installation auto-pluralizes type labels |\n| N/A | Registered workspace labels, aliases, mount state, and default new-note destination |\n| N/A | Mobile AutoGit, appearance, interface scale and analytics consent |\n| Any user-visible customization of how content is organized or displayed | Any machine-specific or credential-type setting |\n\n**Rule:** If the information is about *how the content is structured or presented* and the user would expect it to be consistent wherever they open their vault, store it in the vault (frontmatter of the relevant note, using the `_field` underscore convention for system properties). If it's about *this specific installation of the app*, store it in app config JSON under `$XDG_CONFIG_HOME/com.tolaria.app/` (defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms), or in localStorage for renderer-only transient layout state. The native resolver keeps XDG as the preferred root when the current user can write the target; if that root is not writable, it uses the platform config root instead and reads that writable fallback before stale XDG files. See [ADR-0177](./adr/0177-writable-fallback-for-app-config.md).\n\nMobile installation preferences use the app-owned document configuration\ndirectory with validated pending-file publication and backup recovery (ADR 0212).\nManaged Git vaults retain the existing save/operation queues. Manual sync requires\nan explicit local commit; opt-in AutoGit schedules foreground idle checkpoints\nand synchronization without promising background execution.\n\nMobile Git merges validate synthesized frontmatter and saved-view/configuration\nblobs before publishing the proposed tree (ADR 0220). Invalid metadata leaves\nHEAD, index and working files untouched for explicit conflict resolution.\nGit sync has an independent operation mutex and uses short editor/write barriers\naround checkpoints and incoming checkout, not HTTP waits (ADR 0221). Temporary\nfailures retry with bounded backoff, with Expo network reconnection as a retry\nhint. Authentication and conflict failures require attention.\nInstallation-local sync history is keyed by managed vault ID and published with\nbackup recovery (ADR 0222). It records successful remote/provider operations\nseparately from local commits and pending edits, without storing user content.\nThe same ledger keeps installation-local, revisioned paths for pending note dots\n(ADR 0271). A successful checkpoint acknowledges only the captured revisions;\nedits made while transport is running remain pending. These hints never replace\nfresh Git/content comparisons or authorize checkout.\nNative conflict review shares local/remote/keep-both choices across Git and linked\nfolders (ADR 0223). Git retains both parents; coordinated folder publication\nretains private original snapshots and an interruption journal. Comparisons are\nversion-checked again before applying any choices.\n\nNew iOS folder selections retain a Files-provider bookmark alongside an offline\nworking copy (ADR 0217). Native, coordinated three-way reconciliation writes\nlocal edits back and imports external changes; divergent versions stop for\nresolution. This is independent of Git. Provider delivery, including iCloud's\ncross-device upload, remains the provider's responsibility. Existing copy-only\nvaults are never silently attached to an original folder.\n\nExamples:\n- ✅ Vault: `_pinned_properties` in a Type note (every device should show the same pinned properties)\n- ✅ Vault: `_icon: shapes` in a Type note (icon is part of the type's identity)\n- ✅ Vault: `_width: wide` in a note that already has frontmatter (per-note reading/editing preference)\n- ✅ App settings: `zoom: 1.3` (machine-specific preference)\n- ✅ App settings: `ui_language: \"zh-CN\"` (installation-specific UI language)\n- ✅ App settings: `note_width_mode: \"wide\"` (installation-specific default for notes without an override)\n- ✅ App settings: `date_display_format: \"friendly\"` (installation-specific date rendering preference)\n- ✅ App settings: `sidebar_type_pluralization_enabled: false` (installation-specific sidebar label preference)\n- ✅ App settings: `all_notes_show_images: true` (installation-specific All Notes file-category visibility)\n- ✅ App settings: `git_provider: \"wsl\"` plus `git_wsl_distro: \"Ubuntu\"` (machine-specific Git executable location)\n\n### No hardcoded exceptions\n\nNo field names, folder paths, or vault-specific values should be hardcoded in the application source code. What can be a convention should be a convention. What needs to be configurable should live in a file. Relationship fields are detected dynamically by checking whether values contain `[[wikilinks]]` — no hardcoded field name lists.\n\n### AI-first knowledge graph\n\nNotes are not just documents — they are nodes in a structured graph of people, projects, events, responsibilities, and ideas. Every design decision should ask: \"Does this make the knowledge graph easier for a human *and* an AI to navigate?\" Conventions that are legible to both are better than conventions that are legible only to one.\n\n### Three representations, one authority\n\nVault data exists in three forms simultaneously:\n1. **Filesystem** — the `.md` files on disk. This is the single source of truth.\n2. **Cache** — `~/.laputa/cache/<hash>.json`, an index for fast startup. Always reconstructible from the filesystem.\n3. **React state** — the in-memory `VaultEntry[]` during a session. Always derived from the cache or filesystem.\n\nThese must never diverge permanently. If they do, the filesystem wins and the cache/state are rebuilt.\n\n```mermaid\nflowchart LR\n    FS[\"🗂️ Filesystem\\n.md files on disk\\n(source of truth)\"]\n    Cache[\"⚡ Cache\\n~/.laputa/cache/\\n(fast startup index)\"]\n    RS[\"⚛️ React State\\nVaultEntry[]\\n(in-memory session)\"]\n\n    FS -->|\"scan_vault_cached()\"| Cache\n    Cache -->|\"useVaultLoader on load\"| RS\n    FS -->|\"reload_vault (full rescan)\"| RS\n    RS -.->|\"write via Tauri IPC first\"| FS\n\n    style FS fill:#d4edda,stroke:#28a745,color:#000\n    style Cache fill:#fff3cd,stroke:#ffc107,color:#000\n    style RS fill:#cce5ff,stroke:#004085,color:#000\n```\n\n#### Ownership rules\n\n| Layer | Owner | Writes to | Reads from |\n|-------|-------|-----------|------------|\n| Filesystem | Tauri Rust commands (`save_note_content`, `update_frontmatter`, etc.) | Disk | — |\n| Cache | `scan_vault_cached()` in `vault/cache.rs` | `~/.laputa/cache/` | Filesystem + git diff |\n| React state | `useVaultLoader` + `useEntryActions` + `useNoteActions` | In-memory `entries` | Cache (on load), filesystem (on reload) |\n\n#### Invariants\n\n1. **Disk-first writes**: All functions that change vault data must write to disk (via Tauri IPC) *before* updating React state. This ensures that if the disk write fails, React state remains consistent with what's actually on disk.\n2. **Optimistic UI with rollback**: Where responsiveness matters (e.g. `persistOptimistic` in `useNoteCreation`), state may update before disk confirmation — but a failure callback must revert the optimistic state.\n3. **No orphan state updates**: Never call `updateEntry()` before the corresponding `handleUpdateFrontmatter()` or `handleDeleteProperty()` has resolved. Type metadata actions in `useEntryActions` follow this rule — create the missing type document if needed, write frontmatter first, then update state. If missing type creation collides with an existing note filename, the action stops after the existing creation toast instead of applying orphaned state.\n4. **Recovery via reload**: If state ever diverges from disk (crash, external edit, race condition), `Reload Vault` (Cmd+K → \"Reload Vault\") does a full filesystem rescan via the `reload_vault` Tauri command, atomically replacing the cache and all React state. The `reload_vault_entry` command can re-read a single file.\n5. **Cache is disposable and crash-safe**: A full reload keeps the last valid snapshot readable while it builds a replacement, then swaps the new cache into place only after the scan completes. A crash during refresh therefore cannot turn the next startup into a cache-miss scan. The cache remains derived from the filesystem and can always be deleted and rebuilt.\n6. **Visibility filters are command-boundary concerns**: Gitignored-content visibility is applied after scanning/caching, before entries, folders, or search results reach React. The cache remains complete so toggling the setting can show ignored content again without rebuilding a different cache shape. Large folder filtering runs on the blocking Tokio pool and drains `git check-ignore` output while feeding stdin so broad ignore matches cannot freeze the native UI thread.\n\n#### External Change Detection\n\nEvery full App window registers its active vault roots through `start_vault_watcher` / `stop_vault_watcher` (`src-tauri/src/vault_watcher.rs`, backed by Rust `notify`). The backend reuses one native watcher per shared root, tracks owning window labels, and removes a watcher only after its final owner unregisters. Watchers emit `vault-changed` events for content paths and ignore churn from `.git/`, `node_modules/`, temp files, and `.tolaria-rename-txn`. `useVaultWatcher` batches those events and suppresses recent app-owned saves. The main window sends remaining paths through `refreshPulledVaultState()` so folders, saved views, note-list state, and clean active-editor content refresh under the ADR-0135 unsaved-edit rules. Secondary note windows reload known changed entries individually, preserving the full graph while avoiding full scans; deleted, moved, unknown, or otherwise non-reloadable paths fall back to the shared full reconciler. A same-path replacement keeps the active tab mounted, and a frontmatter-only replacement keeps the current BlockNote document, caret, focus, and scroll position. Content changes still replace the clean editor document, while a true move or deletion clears the obsolete tab; when a document replacement starts from a focused editor, the app dispatches a post-replacement editor focus request. `useVaultLoader.isReloading` drives the status-bar reload spinner for both manual and full watcher-triggered reloads.\n\nWindow focus also requests Git-based external rename detection for the active vault. The shared `commands::blocking` boundary schedules synchronous command work on Tokio's blocking pool and converts join failures to string errors, retaining contextual labels where needed. The `detect_renames` Tauri command uses this boundary for its filesystem and Git subprocess work so a slow repository cannot stall the native event loop while the user resumes typing.\n\n#### Progressive Vault Loading\n\nVault opening waits for the persisted vault registry before selecting a path, so it never scans the legacy default vault on the way to the active workspace. On a warm start, `read_vault_snapshot` deserializes a version- and path-valid active-vault cache without Git, per-entry filesystem checks, sorting, or cache replacement; command-boundary visibility filtering can remove Gitignored entries without discarding that snapshot. React installs the provisional graph and clears `isLoading`, then `list_vault` reconciles Git changes and missing files in the background. Invalid or missing snapshots and forced reloads use the authoritative scan path. Clean same-commit reconciliation avoids both the full Git-date scan and an identical cache rewrite. Folders and saved views load independently; secondary workspaces start only after the active graph is usable and hydrate one at a time. Nested mounts derive their entries from a loaded ancestor instead of scanning the same subtree again. BlockNote and editor-only styles remain outside the initial shell chunk and prewarm after its first commit; selecting a note requests them immediately.\n\nStartup performance is measured end to end with ordered, path-free milestones spanning renderer initialization, persisted settings, vault selection, snapshot hydration, interactivity, editor loading, and background reconciliation. Each milestone records both renderer-relative and native-process elapsed time; `TOLARIA_STARTUP_TRACE=1` writes the same JSON events to stderr for repeatable native measurements without adding UI. Git repository probing runs on the blocking Tokio pool, and missing Git-related environment variables are resolved with one login-shell invocation rather than one process per variable. See [ADR-0166](./adr/0166-snapshot-first-progressive-vault-startup.md) and [ADR-0170](./adr/0170-measurable-crash-safe-startup.md).\n\nLarge-vault reproduction and keyboard QA steps live in [LARGE-VAULT-LOADING-QA.md](./LARGE-VAULT-LOADING-QA.md).\n\n#### Mounted Workspaces\n\nThe registered vault list can act as a mounted-workspace set. `useVaultSwitcher` persists each workspace's installation-local identity (`label`, stable `alias`, color, mount flag) and the default destination for newly created notes in `vaults.json` under Tolaria's app config directory (`$XDG_CONFIG_HOME/com.tolaria.app/`, defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms). If the native app cannot write that XDG target, it falls back to the platform config directory for both the write and the first subsequent read. `useVaultLoader` scans every available mounted workspace and annotates each `VaultEntry` with provenance before React consumes the combined graph. The default workspace is the write target for new notes and Type documents; it is not the only active vault when multiple workspaces are enabled.\n\nExternal MCP clients share that registry boundary. `attach_vault` registers an existing canonical directory without initializing Git, while `clone_vault` uses non-interactive system Git in a temporary sibling and publishes the destination only after clone success. Both preserve the current active vault, add the new root to the calling MCP process immediately, and broadcast a registry-change intent so running renderers reload mounted-workspace metadata without relaunching or switching focus. See [ADR-0175](./adr/0175-mcp-vault-lifecycle-uses-app-registry.md).\n\nThe status-bar vault menu can open a registered vault as an independent desktop application instance. `VaultMenu` owns rendering, while `vaultMenuInteractions`, `vaultMenuActions`, and `vaultMenuTrigger` isolate mounted-workspace behavior, action construction, and trigger styling. Ordinary launches still register the single-instance plugin, while an explicit vault-instance launch bypasses it and scopes that process's active vault without changing the shared registry's ordinary startup selection. Packaged macOS builds use Launch Services `open -n` so each vault receives its own Dock tile and Command-Tab entry. `src/shared/workspaceColorContract.json` is the cross-runtime source for renderer picker keys, launch validation, persisted vault-color normalization, and light/dark native icon tints. Each process's AppKit icon is generated from the bundled Tolaria icon by recoloring its droplet with the launched or active vault's declared native tint; renderer-only `pink` and `gray` colors explicitly fall back to the contract's default blue tint. See [ADR-0171](./adr/0171-separate-vault-application-instances.md).\n\nVault item deep links use the registered vault list as their resolver namespace. `src/utils/deepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from workspace aliases, labels, and paths, appending a short stable hash when generated slugs would collide. `useDeepLinks` validates incoming links, broadcasts cross-window route requests so an already-open matching vault instance can claim and focus the link, then falls back to switching the receiving process's vault when no matching window answers. The owning window reloads the vault index once for recently changed files and opens the matching `VaultEntry` through the normal note-selection path.\n\nSaved Views participate in that mounted graph as source-scoped chrome. `useVaultLoader` loads view definitions from every mounted vault, annotates each `ViewFile` with its owning `rootPath` and workspace identity, and keeps sidebar selection/persistence keyed by `(rootPath, filename)` so same-named view files from different vaults stay independent.\n\nGit surfaces resolve repository paths explicitly. `useGitRepositories` derives the active repository set from the mounted available workspaces, keeps separate selected repositories for Changes, Pulse/history, and manual commits, and exposes the combined modified-file count for status/commands. Native Git capability checks treat a mounted workspace as Git-backed when it is inside any Git work tree, so included vault folders can reuse a parent repository without creating embedded `.git` directories. Rust resolves each vault to a `GitWorkspace` (`vault_root`, provider-discovered `git_root`, and repository-relative `vault_pathspec`). Vault-scoped status, diff, history, dates, pulse, conflict, and commit commands execute at the Git root with that pathspec, then map Git output back to vault-relative note paths. Branches, remotes, fetch, pull, push, merge, and rebase remain repository-wide; the settings row exposes the resolved root and parent-repository sync feedback identifies that wider scope. App-created commits use path-limited staging plus `git commit --only` so sibling staged and unstaged work is preserved. Mounted folders outside Git return no changes/no remote for aggregate probes instead of failing workspace-wide sync. AutoGit checkpoints iterate that repository set, while manual commit, history, diff, and discard operations use the selected surface or the note's workspace provenance. See [ADR-0161](./adr/0161-parent-git-workspace-context.md).\n\nGit command launch is also installation-local. The Rust Git module resolves a `GitLaunchConfig` from app settings before spawning Git, defaulting to native `git` while allowing Windows installations to explicitly select WSL2 Git and a distribution. WSL-selected launches go through `wsl.exe --exec git` and translate Windows/WSL UNC vault paths before passing repository paths across the command boundary; Tolaria never silently switches providers based on detection alone.\n\nRenderer git file workflows stay behind `useGitFileWorkflows`. The hook resolves per-note repository paths, queues editor diff requests, opens Pulse history entries including deleted-file previews, and keeps discard/reload handling close to the selected Git surface while `App.tsx` only wires the resulting callbacks into `NoteList`, `PulseView`, and `Editor`. `useInitialPulseLoad` separately owns cancellable Pulse initial, refresh-key, and retry requests; pagination remains in `PulseView`. Note history reloads only when its note, visibility, or explicit refresh generation changes; changing renderer callback identities during an editor-state update does not rerun `git log`, and the native history command executes that blocking process on Tokio's blocking pool.\n\nCross-workspace note reads and writes keep the disk-first invariant. When an absolute note path is saved or read without an explicit `vaultPath`, the Tauri boundary resolves the deepest registered vault root that contains the path and validates against that root before touching disk. This lets an editor tab opened from a mounted workspace save back to its source repository while preserving the same path-escape protections as active-vault operations.\n\n#### Note Opening Fast Path\n\nNote opening uses bounded in-memory fast paths for raw content and parsed editor blocks. `useTabManagement` owns the markdown/text prefetch cache and treats every cached value as a performance hint only: identity-matched entries (`modifiedAt` + `fileSize`) can be reused immediately, while identity-missing or identity-mismatched cached text is checked with `validate_note_content`, which compares the cached text with the current file bytes inside the validated vault boundary. If validation fails, Tolaria discards the cached entry and reads fresh disk content before swapping the editor.\n\nThe note list opportunistically preloads visible and adjacent markdown/text entries after a short delay. When a large warmed Markdown note resolves, `useEditorTabSwap` may parse it into a bounded parsed-block cache only after foreground editor work has been idle and the rich editor is mounted. On foreground open, block resolution checks the hot tab cache, exact-source parsed-block cache, blank/H1 fast path, and a direct Markdown-to-block parser for large common Markdown before falling back to BlockNote's parser. The direct parser runs in a module worker when available and falls back synchronously only for tests or runtimes without workers. It is conservative: unsupported Markdown constructs return to the BlockNote path rather than changing semantics. Large resolved block sets mount progressively: the editor is locked, the first chunk is applied immediately, remaining chunks append across animation frames, and the same generation/source-content token can abort the partial mount before it is committed. Progressive chunk boundaries expand through contiguous numbered-list items so BlockNote receives each ordered-list run in one mutation and preserves continuous numbering. Every programmatic document replacement, including progressive chunks and fallback recovery, is excluded from ProseMirror history so note navigation cannot retain previous document graphs or displace user typing from undo. Ordinary BlockNote block wrappers stay fully measurable after mount so hover side menus, slash dialogs, and document-end interactions do not inherit browser lazy-height scroll jumps. Parsed blocks are keyed by vault, path, and exact source content; every async swap carries a generation/source-content token so stale conversion results cannot overwrite newer file content or dirty editor state. The editor never renders a preview surface that later morphs into BlockNote. Development builds log `editorBlockResolve`, `editorBlockApply`, and `parsedBlockPreload` timings for large or slow note opens. See [ADR-0105](./adr/0105-editor-correctness-and-responsiveness-contract.md).\n\nRich-editor Markdown boundaries stay on the editor fast path while keeping one owner for import, installation, and export. `src/utils/richEditorMarkdown.ts` installs the direct serializer on real BlockNote editors, preprocesses Markdown before parsing, injects Tolaria schema blocks after parsing, and serializes body/document Markdown for saves, raw-mode entry, tab swaps, and position snapshots. Its lower-level direct serializer (`src/utils/blockNoteDirectMarkdown.ts`) serializes common Tolaria block shapes to Markdown with a per-editor weak cache keyed by stable block object identity and list context. Durable Markdown bridges for wikilinks, linked inline-code labels, math, highlights, files, Mermaid, tldraw, sandboxed HTML, blockquote callouts, and literal path-template backslashes stay behind `richEditorMarkdown.ts`; linked code and vulnerable literal backslashes are protected by Markdown-inert placeholders during import and restored before editing, while the direct serializer keeps safe identifier underscores, Windows-style separators, and line-ending-adjacent formatting delimiters stable. The bridge helpers preserve unchanged block objects so repeated debounced saves can reuse direct Markdown projections. Highlights use portable Bear-compatible prefixes for custom colors (`==🔴red==`, `==🔵blue==`, `==🟣purple==`, and `==🟢green==`) while the prefix-free `==text==` form remains the default yellow. Callouts keep marker metadata in block props but retain their body as editable inline content, and their mixed serializer restores portable `> [!type]` syntax. Stable active-tab rerenders trust the exact-source block cache before doing any full-document Markdown comparison, so unrelated React state changes do not serialize large notes. Unsupported block shapes fall back to BlockNote's lossy serializer, and development builds log `richEditorSerialize` timing/cache/fallback metrics plus rare `editorStabilityCheck` fallback comparisons for large or slow serializations.\n\nRich-editor typing keeps BlockNote's per-transaction work minimal. Tolaria creates BlockNote with animation bookkeeping disabled so the `previousBlockType` extension, which scans the old and new ProseMirror documents on every edit, is not installed. Collapsed-heading rendering attaches its mutation observer and `editor.onChange` subscription only while at least one heading/list section is collapsed, then detaches when the final section expands. Heading folds follow the document outline through divider blocks and stop at the next same-or-higher heading. Development builds wrap ProseMirror dispatch once per editor view and log `richEditorDispatch` timings for large or slow transactions, giving QA a direct signal for edit-time stutter instead of only note-open speed. The pinned `prosemirror-view` dependency normalizes a stale child boundary to the live parent end during DOM-selection synchronization so editor teardown or remount cannot dereference a child removed mid-scan; see [ADR-0183](./adr/0183-normalize-stale-prosemirror-selection-boundaries.md).\n\nCore perceived latency is protected by a synthetic production-browser benchmark. `pnpm perf` injects deterministic 500-entry startup/note fixtures and a 2,000-entry virtualized-list fixture, discards a warmup, then aggregates five measured runs. Startup phases publish standard `tolaria:<phase>` User Timing marks; note scenarios combine visible editor/content milestones with opt-in note/editor trace durations; list scrolling waits through the destination paint. `.editor-performance-thresholds.json` stores ratcheted median budgets and p90 diagnostics. CircleCI runs the gate in a dedicated browser job, while native `TOLARIA_STARTUP_TRACE=1` remains the richer host-level diagnostic tier. See `docs/PERFORMANCE.md` for the instrumentation audit and baseline policy.\n\n## Tech Stack\n\n| Layer | Technology | Version |\n|-------|-----------|---------|\n| Desktop shell | Tauri v2 | 2.10.0 |\n| Frontend | React + TypeScript | React 19, TS 5.9 |\n| Mobile shell | Expo React Native | Expo 54, React Native 0.81 |\n| Editor | BlockNote | 0.46.2 |\n| Editor render extensions | @tiptap/pm | ProseMirror decorations for rich-editor node presentation |\n| Code block highlighting | @blocknote/code-block | 0.46.2 |\n| Additional code grammars | @shikijs/langs | 3.23.0 |\n| Diagram rendering | Mermaid | 11.14.0 |\n| Whiteboard rendering | tldraw | 4.5.10 |\n| Raw editor | CodeMirror 6 + official language packages | Markdown, standalone/fenced HTML, YAML, JSON, Python, SQL, JS/TS |\n| Spreadsheet editor | IronCalc workbook + WASM | 0.5.x |\n| Styling | Tailwind CSS v4 + CSS variables | 4.1.18 |\n| UI primitives | Radix UI + shadcn/ui | - |\n| Mobile UI primitives | Nativewind + React Native Reusables via Tolaria-owned wrappers | Nativewind 4, RNR registry primitives |\n| Icons | Phosphor Icons | Full catalog; per-icon modules load on demand |\n| Build | Vite | 7.3.1 |\n| Backend language | Rust (edition 2021) | 1.77.2 |\n| Frontmatter parsing | gray_matter | 0.2 |\n| Filesystem watcher | notify | 6.1 |\n| AI (workspace) | CLI agent adapters (Claude Code + Codex + GitHub Copilot + OpenCode + Pi + Antigravity + Kiro + Hermes Agent) plus configured local/API model targets | - |\n| Search | Keyword (walkdir-based file scan) | - |\n| Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync |\n| MCP | @modelcontextprotocol/sdk | 1.0 |\n| Tests | Vitest (unit), Playwright (E2E/smoke), cargo test (Rust) | - |\n| Package manager | pnpm | - |\n\nThe icon registry derives its stable kebab-case catalog from the Phosphor CSR\nmodule filenames at build time. Each icon remains a separate Vite dynamic\nimport, so resolving an uncommon vault icon loads only that SVG component.\nThe type customization grid renders the catalog in progressive batches while\npreserving search across every registered icon. See [ADR-0174](./adr/0174-lazy-full-phosphor-icon-catalog.md).\n\n### Mobile App Foundation\n\nStandalone iOS editor text saves use the existing workspace module's asynchronous\natomic writer (ADR 0203). A private, versioned recovery record is published before\nreplacement; restart recovery precedes indexing, reads, and Git synchronization.\nUnexpected destination changes retain the pending draft and fail closed. This\nprotects published text saves, not unsent keystrokes. Structural entry operations\nuse the same recovery boundary: exclusive rename for moves, private quarantine\nbefore deletion cleanup, and UUID completion receipts for safe queue retries\n(ADR 0204). Text and entry retries consult completion receipts before capturing\nnew preconditions, so replay cannot overwrite later edits or delete recreated\nentries (ADR 0206). A move plus its subsequent backlink rewrites is not yet one transaction.\nExpo Go and Android still require their own equivalent persistence implementation.\n\nThe native text engine also supports a version-two journal containing an entire\ntext-only batch (ADR 0207). It preflights all prior digests and retains the plan\nwhen an external edit prevents replay. Single-save journals and receipts remain\nbackward readable. The repository routes entirely text-based multi-write plans\nthrough the native batch capability, coalescing repeated paths to their final\ncontent. Single saves, mixed structural plans, and runtimes without that\ncapability retain their existing individual-operation queue boundaries.\n\nThe same iOS module exposes an optional, single-call Git metadata reader anchored\nto the app sandbox. It returns fresh stat fields without caching file state;\nExpo continues to own byte IO. Rapid-edit staging and checkout recovery remain\nunchanged (ADR 0205).\n\nMobile saved-view loading uses `mobileViewYaml.ts` and the `yaml` parser with\nbounded input, alias expansion and filter depth (ADR 0210). Valid desktop YAML\nindentation and flow collections retain their filters; malformed definitions are\nrejected instead of becoming all-note views. Evaluation and serialization remain\nin `mobileSavedViews.ts`. Sidebar type discovery has no arbitrary count cap and\nuses the desktop pluralization helper, while explicit type labels take priority.\n\n`MobilePanelSafeArea` owns each tablet pane's top inset, background and divider.\nThere is no workspace footer: Settings lives at the sidebar bottom. The phone\nshell colors its top inset from the active panel. Panes and their dividers reach\nthe bottom edge; bottom controls and scrollable inspector content own their\nhome-indicator clearance without leaving a separate band below the workspace.\n`MobileGitHubRepositories` uses a bounded, searchable FlatList picker, and\n`MobileDeviceCode` copies authorization codes without routing secrets through the\ndiagnostic clipboard helper. Inspector relationship groups expose their own add\naction, and native option rows use explicit styles on the RNR button primitive.\n\nThe vault catalog remembers local imports alongside Git working copies (ADR\n0216). Each new local import has its own app-owned directory; the old single\nimport is registered in place once. One mutually exclusive active pointer is\nrestored before workspace loading. Forgetting changes only the catalog, never\nthe vault's files. Settings reuses the vault manager's GitHub account controller\nand hands off vault operations through the existing editor-save barrier.\n\nLazy note hydration reparses only the loaded target, resolves its relationships\nagainst the full note pool, and retains unrelated note object identities. It\ndoes not emit filesystem writes or change modified timestamps. The native\nread-only benchmark measures hydration separately from initial indexing and\ntype filtering; these are data-path timings, not screen-transition timings.\n\nStorage verification can also run against an optimized local binary using the\nexplicit `TOLARIA_QA` Swift condition (ADR 0208). A read-only native capability\nenables the existing loopback-only probes without Metro or development JavaScript.\nOrdinary Release excludes that native API. Git QA records JavaScript timer delay\nseparately from clone/push/pull duration; neither measure represents native frame\nrate or a physical-device performance budget.\n\nPhone layout uses `useMobileDeviceLayout` instead of an orientation-sensitive\nwindow-width breakpoint. Its actual panels occupy fixed slots on one retained\nrail; `usePhonePanelRail` drives that rail through RNGH and Reanimated on the UI\nthread. Incoming panes are not duplicate previews, and settling does not reset\nthe offset or start a second entrance animation. `usePhoneViewportWidth` accounts\nfor the outer safe area once, so landscape panes and edge gestures use the real\ncontent viewport. Inactive content is excluded from input and accessibility;\nthe editor is created on its first visit and retained thereafter. See ADR-0224\nand ADR-0244. Native acceptance covers cancellation, draft/focus continuity,\nscroll position, rotation, note-row actions and corresponding iPad behavior.\n\nThe mobile app lives in `apps/mobile` as a separate Expo React Native workspace package. It is intentionally not a port of the desktop React DOM component tree. Desktop components, CSS variables, and panel semantics are treated as the reference contract, while mobile uses native primitives and fixture-driven screens. Distribution builds use Expo SDK 57 with the supported `expo-build-properties` scene-lifecycle opt-in for Xcode 27/iOS 27 compatibility (ADR 0246). Prebuild generates Expo's scene manifest and window owner; the temporary Expo Go 54 preview constraint is superseded. Native dependencies and scene support require a new signed build and OTA fingerprint, not a JavaScript-only update.\n\nThe first mobile surface is a UI lab with real workspace semantics behind a fast fixture default. `apps/mobile/src/components/ui` owns RNR-derived shadcn-style primitives such as text, buttons, and badges, while `apps/mobile/src/ui` owns Tolaria-specific wrappers such as icon buttons, panels, toolbars, list rows, chips, and property rows. Workspace components live under `apps/mobile/src/components/workspace` and consume `MobileWorkspaceSnapshot` from `apps/mobile/src/workspace/mobileWorkspaceModel.ts`. Tablet panel gestures use one workspace-level horizontal responder and Reanimated offsets. The sidebar and note list remain mounted as one strip with desktop-token snap points at 0, -260, and -600 pixels, so a drag begun anywhere in workspace content can directly hide or reveal both panels without lifting. Only a hidden Properties panel reserves a 32-pixel right-edge reveal gesture; the editor toolbar exposes the equivalent explicit toggle. The native panel-gesture QA maps React Native accessibility frames onto the rotated Simulator window, injects sampled real drags from content and the screen edge, and verifies each settled panel state through stable test identifiers. The default repository remains fixture-backed for screenshot speed, while the Playwright QA harness can inject a real read-only local-vault snapshot through `tolaria:mobile-workspace-snapshot` when `MOBILE_QA_VAULT_PATH` is set. Explicit `source=native-vault` mode uses `expo-file-system` through `apps/mobile/src/workspace/expoWorkspaceFileSystem.ts`, reads an Expo-accessible vault directory, and persists reducer write plans back to markdown files and directories. On iOS, selecting a vault imports it atomically into the app-managed `Documents/Tolaria Vault` directory because document-picker access to arbitrary external folders cannot be reacquired after process termination; the custom `TolariaWorkspaceAccess` Expo module restores that managed local copy on cold launch and the app exposes Documents through Files. For launch performance, that module enumerates the managed vault off the JavaScript thread and returns one serialized file index to the existing pure snapshot builder, avoiding thousands of per-file React Native bridge calls; editor hydration and reducer write plans continue to use the filesystem repository rather than treating the launch index as a second persistence model. `apps/mobile/src/workspace/localVaultSnapshot.ts` keeps scanning as a pure files-plus-folder-paths transform: it parses Tolaria frontmatter conventions, preserves explicit empty folder paths for the sidebar, derives type colors from Type documents, caps rendered note rows for fast UI work, and is shared by host-vault QA and native-vault mode.\n\nTablet editing is reducer-owned and repository-persisted. `apps/mobile/src/workspace/mobileWorkspaceEditing.ts` applies note creation, title/body edits, frontmatter scalar property changes, relationship add/remove, archive, favorite, folder create/rename/delete, saved-view definition changes, primary note-list display-property config changes, and Type definition actions against the current `MobileWorkspaceSnapshot`. Note folder moves and explicit filename renames emit `moveNote` file-write plans plus only the `saveNote` writes needed for backlink rewrites, while folder edits update explicit folder paths, retarget notes in renamed subtrees, rewrite affected path-based wikilinks, and emit directory write plans instead of modeling folder renames as note delete/save loops. Saved-view edits persist desktop YAML fields including filters, `order`, and `listPropertiesDisplay`, so mobile view ordering and saved-view note-list column choices use the same file contract as desktop. Primary All Notes and Inbox display-property choices emit `saveVaultConfig` plans and are stored outside the vault file list by the repository adapter, matching the desktop vault-path-local config contract. Type document create/delete/update/reorder logic lives in `apps/mobile/src/workspace/mobileWorkspaceTypeEditing.ts` and persists the Type markdown document frontmatter for sidebar label, visibility, color, sort, note-list display properties, and instance schema/default fields through `apps/mobile/src/workspace/mobileTypeDefinitions.ts`; `apps/mobile/src/workspace/mobileTypeDefinitionSchema.ts` owns the form conversion between custom scalar properties, wikilink relationship fields, and blank built-in relationship placeholders. `apps/mobile/src/screens/tabletWorkspaceHistory.ts` records the mobile command history at the snapshot boundary: content/frontmatter edits replay as content writes, structural note, folder, saved-view, and Type-definition changes replay through exact restore edits, and `apps/mobile/src/screens/tabletWorkspacePathHistory.ts` routes note moves, note filename renames, and folder subtree renames back through inverse reducer commands so undo/redo preserves filenames, ordering, raw Markdown, backlink rewrites, and persisted write paths. `apps/mobile/src/workspace/mobileNeighborhood.ts` mirrors the desktop Neighborhood note-list contract for mobile snapshots by pinning the source note, grouping Type instances, direct relationships, inverse relationships, and Backlinks with the same desktop group labels, and preserving duplicate notes across groups. The reducer preserves frontmatter while the markdown editor edits body content, derives snippets/blocks/link targets back from Markdown, resolves wikilink relationship targets against the snapshot, and returns write plans. The source editor and web WYSIWYG fallback keep keystrokes in a local draft and flush through that reducer after a short idle window or when editing unmounts, matching the desktop edit-buffer boundary instead of rebuilding the workspace graph for every character. Mobile note snapshots preserve frontmatter aliases so wikilink resolution and editor autocomplete can match desktop identity candidates. The repository boundary decides whether those writes are no-ops for fixtures, host-injected QA logs, installation-local config writes, or native Expo filesystem writes. Visual components do not import filesystem APIs directly.\n\nThe native mobile WYSIWYG editor is wrapped behind `MobileWysiwygMarkdownEditor`. It registers Tolaria-owned TenTap bridge extensions only at that boundary and passes a generated Tolaria TenTap WebView source through `customSource`, so custom Tiptap nodes exist inside the editor WebView instead of only in React Native. `MobileWysiwygContentBridge.ts` hydrates programmatic document content without emitting user-edit updates, and the React Native lifecycle keeps persistence gated until an explicit touch reaches the editor surface; lazy note hydration therefore cannot be mistaken for a user edit, while the first intentional keystroke still saves normally. `MobileWysiwygMathBridge.ts` adds the `mathInline` and `mathBlock` nodes required for desktop math parity and renders both through the mobile KaTeX MathML helper. `MobileWysiwygTentapWebEditor.tsx` plus `apps/mobile/scripts/build-wysiwyg-editor-html.mjs` own the rebuildable WebView bundle. `mobileInlineMath.ts`, `mobileDisplayMath.ts`, `mobileDocumentContent.ts`, and `mobileWysiwygInputTransforms.ts` keep parsing, hydration, structural input transforms, and Markdown serialization pure and testable. Native WYSIWYG QA probes live beside the mobile editor boundary and are opened through Expo deep links; external links now have a dedicated simulator proof that applies and removes a TenTap link command, then verifies the serialized Markdown output rather than relying only on model tests.\n\nMobile slash commands and inline Find/Replace use that same TenTap boundary. Their ProseMirror plugins own query matching, decorations, keyboard selection and undoable document changes inside the WebView; narrow bridge messages expose state and actions to native RNR controls. Find uses the desktop's pure matching and safe-regex helpers without importing desktop UI. The mounted editor registers Find/Replace with the existing editor-command registry, so keyboard shortcuts and overflow actions reach the inline bar instead of a workspace modal. Source-mode Find uses the same bar and overlays its active match in the existing Markdown syntax layer, retaining YAML frontmatter. Tests mount the actual TenTap web runtime to avoid mixing incompatible ProseMirror instances, and native XCTest checks touch targets, inline placement and serialized Markdown.\n\nMobile note deep-link copy mirrors the desktop URL contract without importing desktop React DOM modules. `apps/mobile/src/workspace/mobileDeepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from the mobile snapshot source and selected note path, while `apps/mobile/src/workspace/mobileClipboard.ts` writes through Expo Clipboard. The action is exposed through the tablet controller/action-sheet callback boundary so visual rows remain platform-agnostic.\n\nMobile note PDF export follows that same boundary. `apps/mobile/src/workspace/mobilePdfExport.ts` strips YAML frontmatter through the mobile document-content helper, renders the note body to Tolaria-styled HTML, and uses `expo-print` plus `expo-sharing` only when running in the native Expo app. Browser-based UI lab checks record deterministic export attempts instead of opening a print dialog, and the generated PDF is treated as a temporary share artifact rather than a new vault file.\n\nMobile attachment import and opening also follow the controller/editor callback boundary. `apps/mobile/src/workspace/mobileAttachmentImport.native.ts` uses `expo-document-picker` plus the Expo FileSystem object API to copy selected files into the active vault's `attachments/` directory, while `mobileAttachments.ts` owns safe filenames and portable Markdown image/link serialization. `apps/mobile/src/workspace/mobileAttachmentUris.ts` is the native-boundary adapter that resolves portable `attachments/...` Markdown targets to Expo file URIs for TenTap/link opening, then converts vault-local file URIs back to portable Markdown before save. Source and WYSIWYG editor surfaces receive importer/open-link callbacks and attachment payloads instead of importing native picker, sharing, or filesystem modules directly.\n\nThe mobile command palette mirrors the desktop command boundary without importing desktop React hooks. `apps/mobile/src/workspace/mobileCommandPalette.ts` builds enabled mobile command rows from existing workspace callbacks, preserving shared IDs from `src/shared/appCommandManifest.json` when a command is manifest-backed and using desktop dynamic command-palette IDs for rows such as note retargeting, file utility actions, and note-width changes. Selection-sensitive editor actions such as paste-without-formatting are registered by the mounted source or native WYSIWYG editor through `apps/mobile/src/workspace/mobileEditorCommands.ts`, so tablet and phone palettes can dispatch the active editor command without importing editor internals. `apps/mobile/src/components/workspace/MobileCommandPalette.tsx` owns only the native searchable/grouped surface; create/search/navigation/note/file/view/settings execution remains in the reducer/controller callbacks. Hardware keyboard commands are exposed through the tracked `TolariaKeyCommands` Expo module: its React delegate handler supplies the root UIKit controller and registers priority `UIKeyCommand`s, then forwards normalized events to the same JavaScript router used by DOM keyboard QA. The root controller accepts first-responder status when no text input owns focus. JavaScript retains one subscription across renders and reads current handlers through a ref. Cmd+1/2/3 and Cmd+Shift+I use the existing tablet panel controller. Unmodified arrows belong to the focused native text input, the open command palette, or the visible note list, in that order; modal/hidden lists cannot consume them. The palette explicitly registers its lifetime with the native module, receives arrows/Escape there, and uses the input's submit event for Return. Native XCTest exercises modifier shortcuts across one workspace session; `assert-ios-keyboard-navigation.mjs` uses AXe HID events and actual accessibility results for arrows/Return/Escape, which XCTest on iOS 18.3 cannot reliably synthesize. Neither harness treats bridge-event logs alone as a successful interaction.\n\nThe mobile launcher name is `Tolaria`; the existing package and bundle identifiers\nremain unchanged so native updates retain their application data.\nThe Expo launcher icon uses opaque 1024px light/dark variants in\n`apps/mobile/assets/icon-light.png` and `icon-dark.png`. They preserve the desktop\ndroplet paths and gradients from `src-tauri/icons/icon-source-dark.svg`, replacing\nonly its macOS rounded tile, shadow and transparent outer margin with full-bleed\nlight/dark backgrounds. iOS owns the launcher mask, avoiding an inset white tile.\nExpo supplies the native sizes and appearance catalog. Desktop assets remain\nunchanged. Rebuild the native app after changing these assets or configuration.\n\nThe Expo dynamic configuration keeps development on `com.tolaria.mob"},"files":{"GEMINI.md":"---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Gemini CLI compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n","AGENTS.md":"# AGENTS.md — Tolaria App\n\n## 1. Development Process\n\n### Mandatory-rule exception protocol\n\nUse an exception only when a required service or analyzer remains unavailable after one retry, or when satisfying the rule is technically impossible without increasing security or data-loss risk. The repository owner is the sole approver. Before proceeding, record the blocked command, evidence, affected files, risk, compensating check, approver, and an expiry of at most 72 hours in the Linear issue or final handoff; review and remove the exception within seven days. A preference, deadline, or failing quality gate is not sufficient exception criteria.\n\n### Task tracking (Linear)\n\n**Linear is the only live queue (cutover 2026-10-15).** Desktop work lives in team Tolaria (`TOL`), project **Tolaria Desktop**; mobile work in **Tolaria Mobile**; web work in project Tolaria Web. Do not create or move Todoist tasks. The old Todoist Tolaria project is legacy (its Draft section is being triaged separately); a completed Todoist card commented \"Moved to Linear: TOL-n\" points to its Linear issue.\n\n**Linear** — workspace Refactoring, team Tolaria, issue key `TOL`.\n\n- Workflow: **Backlog** (not ready) → **Open** (ready for an agent) → **To Rework** (review failed: read the latest review comment and fix only that; pick these **before** Open) → **In Progress** (claimed) → **In Review** (pushed, awaiting Luca's QA) → **Done** / **Canceled** / **Duplicate**.\n- Claim an issue by moving it to In Progress **before** starting work. Never take another worker's In Progress issue; resume your own only when a prior claim comment identifies this worker and worktree. Re-read the issue after claiming and stop on a conflicting claim. Exception: follow the mandatory-rule exception protocol if Linear remains unavailable after one retry.\n- Labels: type `Bug` / `Feature` / `Improvement`; `mac-only` (leave for the Mac mini); `linux-ok`; `agent-blocked` (always with a comment saying why it is stuck and what would unblock it); `dubious` and `contributor-pr` (Backlog, awaiting Luca); Source group `Sentry` / `Canny` / `GitHub`. Exception: an unavailable Linear labeling service requires the same owner-approved exception protocol.\n- Reference the issue ID in commit messages, e.g. `fix: keep image width after restart (TOL-123)`.\n\n### Start working on a task\n\n**Before writing a single line of code:** inspect the configured CodeScene project's current Hotspot and Average Code Health and compare them with `.codescene-thresholds`. Then capture the file-level Code Health score for every existing code file you intend to edit. If the project is already below the threshold, **stop and refactor first** — find the worst files with the MCP, improve them, commit, then start the task. If the gate cannot be restored, stop and obtain explicit repository-owner direction before starting feature work.\n\n- Read task description and all comments fully\n- For To Rework: the ❌ QA failed comment tells you exactly what to fix\n- Check `docs/adr/` for relevant architecture decisions before structural choices\n- Check `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md` for relevant structural information\n- For UI tasks: study app visual language and components first. Prioritize reusing existing components, assets, and variables over recreating them.\n- If working on a Linear issue, add a comment: `🚀 Starting work on this task. [Brief description of approach]`\n\n### Commits & pushes\n\n- Local work may happen on `main`, in detached HEAD worktrees, or in other temporary local states. The production path is still direct-to-main: final verified work is pushed to `origin/main`, with no PR branch flow.\n- Mobile foundation is integrated into main. New mobile work follows the same direct-to-main path; the historical foundation-branch fast lane is not the current delivery workflow.\n- Commit every 20–30 min: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`\n- Pre-commit is a lightweight lint gate only. Pre-push runs the full check suite (build + tests + coverage + core Playwright smoke + CodeScene), preferably on three Chunk sidecar lanes for automatic test/coverage work: frontend lint/build/coverage, Rust coverage, and Playwright smoke. The goal is lower wall-clock time than local hooks while keeping each heavy gate isolated; keep local Playwright mainly for authoring, focused reproduction, or sidecar outages.\n- CircleCI is the authoritative outer-loop CI/CD system. `.circleci/config.yml` owns validation, native cross-platform release builds, GitHub Release publication, and Pages publication.\n- **A task is NOT done until `git push origin main` succeeds.** If the hook blocks: read the error, fix it (clippy, tests, CodeScene, build), commit the fix, push again. If a verified push is impossible, stop and obtain explicit repository-owner direction; do not use `--no-verify` without that recorded approval.\n\n### Mobile coding and release\n\nThe general coding, quality, localization, analytics, credentials and completion rules apply unchanged. Read the mobile sections of `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md`; ADRs 0247, 0263 and 0272 define OTA delivery and channel selection.\n\n- Reuse `apps/mobile/src/components/ui` native primitives and `apps/mobile/src/ui` wrappers, not desktop DOM controls. Keep phone/tablet behavior on the shared controller/repository boundary. Run `pnpm mobile:lint`, `pnpm mobile:typecheck`, `pnpm mobile:test` and focused mobile regressions as well as the normal main push gates; scoped mobile checks do not replace them.\n- Verify affected native behavior separately on iPhone, iPad and Android. Browser checks are not native acceptance. Record task-specific owner delegation of manual QA honestly; it does not waive automated gates. A locked Mac leaves affected checks pending while independent work continues.\n- QA is confined to owned disposable vaults/simulators; existing user apps, containers and vault data stay untouched.\n- CircleCI publishes mobile-related main pushes to **Alpha** after its required gates; desktop-only changes are excluded from mobile publication. Confirm the actual publication receipt, source SHA, runtime and update ID before claiming delivery. Native/configuration/dependency changes that alter the runtime require a compatible signed binary, not an incompatible OTA.\n- Use one external **Tolaria Beta** TestFlight group. Settings defaults to **Stable**, with Alpha opt-in; selected channel and running update identity are distinct. Legacy Alpha-native binaries can launch embedded Alpha before preferences load. Retain runtime matching, safe editor flush/restart and Sentry artifact verification.\n- **Stable promotion remains manual**, authorized explicitly by the owner and using the existing exact-tested-Alpha-artifact republish/approval workflow. Never rebuild from moving main or redirect Stable to moving Alpha. Do not submit Apple compliance declarations or grant new credential permissions without explicit owner authorization.\n\n### Hourly mobile queue routine\n\nKeep the scheduled prompt limited to queue orchestration; this file owns coding and release policy.\n\n1. Reuse the routine's attached worktree and inspect current work. Resume its own identified unfinished claim before taking another issue; keep execution serial under the claim protocol above.\n2. Otherwise paginate **Tolaria Mobile** To Rework, then Open; order by priority (unspecified last), then oldest creation time. Skip others' assigned/delegated work, `agent-blocked` issues and unmet dependencies. Do not pull Backlog or In Review into implementation. Follow the claim protocol above.\n3. Implement the selected issue under this file's rules. On a blocker, record the evidence and required owner action in Linear, preserve a safe checkpoint, and continue independent eligible work without mixing partial changes or extending exceptions.\n4. Put verified implementation In Review with its release record, not self-approved Done. Report meaningful milestones or actionable blockers; stay quiet when the queue or status is unchanged. Resume remaining work on the next hourly run. Keep only one active mobile queue routine.\n\n### TDD (mandatory)\n\nRed → Green → Refactor → Commit. One cycle per commit. For bugs: write failing regression test first, then fix. Exception: pure CSS/layout changes.\n\n**Test quality (Kent Beck's Desiderata):** Isolated · Deterministic · Fast · Behavioral · Structure-insensitive · Specific · Predictive. Fix flaky tests first. Prefer E2E over unit tests for user flows.\n\n### Localization (mandatory for UI copy)\n\nAll user-facing UI labels/copy must live in `src/lib/locales/en.json` and be translated into every target listed in `lara.yaml`. Exception: when the localization service remains unavailable after one retry, use the mandatory-rule exception protocol above and do not release untranslated copy without repository-owner approval. When adding or changing interface copy:\n\n```bash\npnpm l10n:translate\n```\n\nUse `pnpm l10n:translate:force` only when intentionally regenerating existing translations. Commit `src/lib/locales/*.json`, `lara.yaml`/`lara.lock` changes if produced, and verify placeholders/product names stayed intact.\n\n### Product analytics (mandatory for meaningful features)\n\nNew features should emit a PostHog event so we can see whether users actually discover and use them. Skip instrumentation when the change has no meaningful user action or when a dedicated event would create noise; record that reason in the completion comment. If the correct instrumentation remains unclear, obtain repository-owner direction before release. Use clear, stable event names, avoid PII or note content, and include only safe metadata that helps evaluate adoption and failures.\n\nWhen adding or changing a meaningful user-facing feature, include the event name(s) in the Linear completion comment alongside QA, docs, and code health. If intentionally not instrumenting a feature, explain why in the completion comment.\n\n### Code health (mandatory)\n\nPre-push compares **Hotspot Code Health** and **Average Code Health** with the exact numeric floors in `.codescene-thresholds` and fails when either score is lower. Pre-commit is lint-only; CodeScene remains mandatory through the file-level review rules below and the pre-push ratchet gate. Thresholds are a **ratchet** — only go up. When pre-push sees improved remote scores, it updates `.codescene-thresholds`, stages it, and stops so you can commit the new floor with normal verified hooks before pushing again. Exception: adding `// eslint-disable`, `#[allow(...)]`, `as any`, or another bypass requires the mandatory-rule exception protocol and repository-owner approval before the edit.\n\n**Release rule:** CodeScene is a before/after gate, not just a final score. Record the starting CodeScene state before edits and the final state after edits. If touched code gets worse, refactor before committing; if the comparison cannot be produced, stop and obtain explicit repository-owner direction.\n\nDo not edit `.codescene-thresholds` to lower the values. If the gate blocks you, improve the code; any proposed exception requires explicit repository-owner approval recorded before the edit.\n\n**CodeScene access order:** use CodeScene MCP tools if available. If MCP is unavailable, use the installed `cs` CLI for file-level review/delta work, and use the CodeScene API (`CODESCENE_PAT` + `CODESCENE_PROJECT_ID`) for project-wide Hotspot/Average threshold checks from `.codescene-thresholds`.\n\n**Before editing any existing code file:** capture its current file-level CodeScene score. After your edits, re-run the same file-level review and verify the score is higher. If the file already starts at `10.0`, it must remain `10.0`. Exception: if the score cannot be obtained after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before editing.\n\n**New files:** every new **scorable code file** must reach CodeScene score `10.0` before commit. If CodeScene reports `null` / \"no scorable code\" for a new file, it must still have zero CodeScene findings/warnings. If either requirement cannot be met, stop and obtain explicit repository-owner direction.\n\n**Before every commit:** run CodeScene file-level review on every touched or newly created code file and verify the rule above. Then run `mcp__codescene__pre_commit_code_health_safeguard` for the repository and do not commit unless its quality gates pass. **Boy Scout Rule:** every file you touch must leave with a higher score, unless it was already `10.0`, in which case it must stay `10.0`. If an analyzer or gate is unavailable, stop and obtain explicit repository-owner approval for a documented exception.\n\n**Before the final direct-to-main push:** run `mcp__codescene__analyze_change_set` with `base_ref=origin/main`. This is Tolaria's PR-preflight equivalent: every affected file must be improved or stable, and the overall quality gate must pass. Refactor and repeat if any file is degraded. Exception: if the analysis cannot complete after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before pushing.\n\n**If CodeScene gate blocks your push:** use `mcp__codescene__code_health_score` to find the worst file, refactor it, commit, push again. Do not wait for laputa-refactor because it is a background loop rather than a substitute for fixing your own regressions; if no in-scope refactor can restore the gate, stop and obtain explicit repository-owner direction.\n\n### Security scan with Codacy (mandatory)\n\nUse Codacy as a security and static-analysis gate before a task is considered releasable.\n\n- **Two required checks:** Codacy's local CLI/MCP analyzes the uncommitted working tree, while the Codacy dashboard also runs server-side tools after a commit is pushed. These are complementary gates. Report each count with its source; if either check is unavailable, stop and obtain explicit repository-owner approval instead of substituting one for the other.\n- **What counts as new code:** both a newly created code, test, script, or executable configuration file and any added code in an existing file. Scan the entire containing file, not only the added lines, because Codacy tools can report findings on unchanged lines or at file level after a change.\n- **Build the scan manifest:** before each Codacy check, enumerate added and modified tracked paths with `git diff --name-only --diff-filter=ACMR \"$(git merge-base HEAD origin/main)\" --` and untracked paths with `git ls-files --others --exclude-standard`. Classify every code, test, script, and executable configuration path as new or existing, record the manifest in the task evidence, and scan every path individually. Rebuild the manifest immediately before commit so no late-created file is omitted.\n- **Before editing existing code:** run a local file scan and record every finding for each code file you intend to touch. Also record that file's findings on Codacy's currently analyzed `main` commit using `codacy_list_files` to resolve the file and `codacy_get_file_issues` to enumerate all paginated results.\n- **Local file scan:** use `mcp__codacy__codacy_cli_analyze` with the repository's absolute `rootPath` and an explicit `file`, one touched/new file at a time. If the MCP is unavailable or fails, run `LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 .codacy/cli.sh analyze <path> --format sarif --output /tmp/codacy-<slug>.sarif`. Omit `--tool` so every locally configured analyzer runs. Explicitly inspect every `runs[].results[]` entry in the SARIF output.\n- **Analyzer-parity gate:** before creating or modifying code, call `codacy_list_repository_tools` and record the enabled dashboard analyzer name, UUID/generation, configuration mode, and language coverage. Compare that roster with the `runs[].tool.driver` entries produced locally for every language in the scan manifest. Tool names alone are not sufficient: deprecated ESLint/ESLint8 and current ESLint9 are different analyzers, and a local run of Lizard, Opengrep, and Trivy does not prove that dashboard ESLint will be clean. For TypeScript/JavaScript, the passing roster must include the current analyzer that honors `eslint.config.js`, plus every other enabled relevant dashboard tool. If the intended analyzer is disabled, a deprecated analyzer is enabled, or any enabled dashboard analyzer has no equivalent successful local run, analyzer parity fails: stop, correct the Codacy tool configuration, trigger a full reanalysis, and verify parity before code work. A repository-owner-approved exception is required if configuration access is unavailable.\n- **Approved analyzer-parity exception (Biome):** Biome is enabled on the Codacy dashboard but missing from the local Codacy CLI. Biome: covered locally by the repo's own Biome (`npx biome check <changed files>`, or the repo's existing Biome run in `pnpm codacy:gate`); approved by Luca 2026-10-10. This is a standing exception recorded here, so it needs no per-task re-approval: record that `npx biome check` run as the local Biome row in the parity and per-file evidence. It covers only the missing local Biome run; any Biome diagnostic on changed files still fails the gate, and every other analyzer must still match.\n- **Per-file analyzer evidence:** for every new code file, record one result row per expected analyzer with the exact file path, analyzer identity, completion status, and finding counts split into Critical/High/Medium/Minor/Info/unclassified. Missing rows, a skipped/untracked target, an analyzer crash, or an analyzer that silently omits the language is a failed scan, never a zero. If the CLI requires Git discovery, add the file to the index normally before the final scan; do not rely on a directory scan to discover an untracked file.\n- **Fail closed on partial scans:** a zero process exit code is not enough. Confirm the expected relevant tools produced SARIF runs and inspect scan logs for crashes, encoding failures, skipped target files, partial analysis, or missing tool output. If any relevant analyzer did not complete, the scan did not pass.\n- **Approved analyzer-coverage exception (unsupported or excluded files):** if Codacy or CodeScene does not support a file type, or excludes the path from analysis (for example test paths, `.husky/*` hook scripts), an agent does not need a pre-edit baseline or a post-edit score from that tool to edit the file. Record \"not analyzed by <tool>: <unsupported|excluded>\" as that tool's row in the parity/per-file evidence. Likewise, when an enabled dashboard analyzer has no local equivalent for a file type (for example Jackson Linter for `src/lib/locales/*.json`), cover it locally with the repo's own check (`node scripts/validate-locales.mjs` plus a JSON parse for locale catalogs) and record that run. Approved by Luca 2026-10-13; standing, no per-task re-approval. Every other gate still applies: the other tool's baseline and Boy Scout rule where it does analyze the file, zero new findings, tests, coverage, pre-push hooks and post-push verification.\n- **Zero-new-findings rule:** added or modified code must introduce no Codacy finding at any severity, including Info/Minor, Warning/Medium, High/Error/Critical, unclassified findings, and findings reported on unchanged lines or at file level because of the change. If this cannot be achieved, stop and obtain explicit repository-owner approval for a documented exception.\n- **New code — absolute zero rule:** every new code file and every file containing newly added code must be scanned explicitly after creation or modification and must have exactly zero findings attributable to the new code at every severity. A newly created file must have exactly zero findings in the whole file. For added code in an existing file, any finding on an added line, caused by an added symbol or dependency, or newly appearing at file level fails the gate. `0 Critical + 0 High + 0 Medium + 0 Minor + 0 Info + 0 unclassified` is the only passing result. If a scanner cannot prove this, stop and obtain explicit repository-owner approval before proceeding.\n- **Boy Scout Rule for existing files:** after editing, every touched file must have fewer local findings than its recorded baseline; a zero-finding file must stay at zero. Fix every existing Critical/High finding in a touched file; if this cannot be achieved, stop and obtain explicit repository-owner approval for the documented exception.\n- **Before every commit:** re-scan every touched/new code file individually and compare the complete SARIF results with its recorded baseline. Do not rely on added-line filtering, repository totals, or `pnpm codacy:gate` alone; if an individual scan cannot complete, stop and obtain explicit repository-owner approval.\n- **Post-push dashboard verification:** after `git push origin main`, wait until `codacy_get_repository_with_analysis` reports the exact pushed SHA as `lastAnalysedCommit`. Re-read the enabled-tool roster, paginate `codacy_list_repository_issues` to completion, and filter by each exact manifest path; use file-level MCP queries as supporting detail, not as a substitute for the complete repository issue list because newly created files may not yet resolve through file lookup. Absence from the issue list counts as zero only when the exact SHA is analyzed, the analyzer roster still matches the pre-edit parity record, and the repository reports no analyzer problem. Every new code file must have zero dashboard findings at every severity from every enabled analyzer; every existing touched file must have no new finding and must have fewer findings than its pre-edit dashboard baseline (or remain at zero). If the dashboard reports a finding—even one from a deprecated or misconfigured analyzer—the task remains unfinished until the file is fixed or the analyzer configuration is corrected and a replacement exact SHA is fully reanalyzed. Exception: if verification remains unavailable after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- **Repository-wide dashboard counts:** use `codacy_get_repository_with_analysis` for the total and `codacy_list_repository_issues` with full pagination for severity/tool breakdowns. Derive the provider/organization/repository from the Git remote without printing credential-bearing remote URLs; for this repository use `gh` / `refactoringhq` / `tolaria` and branch `main`.\n- **Escalation:** if a scanner is unavailable or a finding is demonstrably false, stop and obtain explicit repository-owner approval recorded in the completion comment. Rule suppression requires that same explicit approval and documentation.\n- `pnpm codacy:gate` is a required fail-closed added-line safety net in pre-push and CI; it does not replace the touched-file before/after check.\n\n### Check suite (runs on every push)\n```bash\npnpm lint && npx tsc --noEmit && pnpm test && pnpm test:coverage  # frontend ≥71%\ncargo test && cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85\n```\n\nCoverage is a release gate, not a vanity metric:\n- Frontend coverage must stay ≥71%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- Rust line coverage must stay ≥85%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- For bug fixes, add a regression test when practical.\n- For new behavior, add targeted coverage close to the changed code; do not rely only on broad E2E coverage.\n\n### UI and native QA\n\n**Phase 1 — Playwright (only for core user flows):**\n\nWrite Playwright test in `tests/smoke/<slug>.spec.ts` when the feature touches vault open, note create/save/delete, search, wikilink navigation, git commit/push, or conflict resolution. Tag a test with `@smoke` when it protects one of those core pre-push workflows; keep cosmetic or mock-heavy checks in the full regression lane. Prefer `.chunk/run-playwright-smoke.sh` on a Chunk sidecar for the curated smoke lane because local Playwright is expensive; keep `pnpm playwright:smoke` available for focused local reproduction. The curated smoke suite must stay under **5 minutes** when sharded on sidecars. Exception: if a platform outage prevents the smoke test from running after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n\n```bash\npnpm dev --port 5201 &\nsleep 3\nBASE_URL=\"http://localhost:5201\" npx playwright test tests/smoke/<slug>.spec.ts\n```\n\n**Phase 2 — Native app QA (macOS):**\n\n> On a Linux agent box, skip this Phase 2 block and follow **Linux agent boxes (Hetzner / remote)** below instead.\n\n```bash\npnpm tauri dev &\nsleep 10\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh laputa\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/qa-native.png\n```\n\nUse computer-use/browser-control interaction for native UI QA when either tool is present in the current environment: click, hover, drag, select, scroll, and type the way a real user would with the mouse and trackpad. For every UI feature, test the primary mouse-driven path first, then verify each keyboard shortcut or keyboard-first workflow implemented or modified by the task. If neither interaction tool is present, run the scripted focus, screenshot, and shortcut checks and document that limitation.\n\nUse `osascript` for app focus, keyboard shortcuts, and keyboard-specific checks. **⚠️ WKWebView:** if an `osascript keystroke` fails to enter editor text, use computer use for the native editor interaction and rely on Playwright for deterministic text-input coverage. Write the result as a Linear comment (✅ or ❌), or in the final handoff when no Linear issue exists.\n\n### Linux agent boxes (Hetzner / remote)\n\nThese rules apply when the agent runs on a Linux box (no macOS desktop, no `osascript`, no `~/.openclaw`). The macOS instructions above stay in force for the Mac mini; do not remove or weaken them.\n\n**Mac-native tasks stay with the Mac mini.** Do not claim tasks labeled `mac-only`, or tasks that clearly need macOS/Windows-native surfaces: updater / auto-update, Homebrew, macOS menus and Cmd/Option shortcut wiring, native file/open/save dialogs, signing/notarization, Windows installers. Leave them in the queue for the Mac mini.\n\n**QA on Linux = the Linux Tolaria build with a throwaway vault.** There is no macOS native QA on these boxes. Build the Linux app (`CI=true pnpm tauri build --debug --no-bundle`) and drive it under Xvfb with a throwaway copy of `demo-vault-v2/` — on the agent boxes use the installed `tolaria-qa` CLI (`tolaria-qa help`: `start`, `click`, `key ctrl+…`, `xtype`, `shot`, `smoke`, `stop`). Never open, create, or modify `~/Laputa` or any real user vault. Test the primary mouse path first, then each keyboard shortcut you added or changed (`Ctrl` on Linux where macOS uses `Cmd`). Phase 1 Playwright still applies. Record QA (✅/❌, what was clicked/typed, screenshots) in the task comment as usual.\n\n**Before pushing to main:** the full check suite passes; then `git fetch origin && git rebase origin/main` (stop on conflicts you cannot resolve safely), re-run the checks (including the Linux build and a Linux app smoke) on the rebased HEAD, then `git push origin main` so the pre-push hook runs against the latest `origin/main`. Never `--no-verify` without a recorded repository-owner exception.\n\n### Release-readiness checklist\n\nBefore pushing or moving a task to In Review, verify the release gates and add a **completion comment** to the Linear issue. Exception: when the work has no Linear issue, put the identical evidence in the final handoff and identify it as the release record. The record must include:\n\n- What was implemented (a few lines covering logic and UX/UI).\n- QA: what was tested and how (Playwright / native screenshot / osascript on macOS; Linux app driven with a throwaway vault on Linux agent boxes).\n- Tests/coverage: commands run and final coverage result.\n- CodeScene: before/after touched-file checks, the pre-commit safeguard verdict, the final `origin/main` change-set verdict, plus final Hotspot and Average scores after push; every gate must pass `.codescene-thresholds`, or the record must include explicit repository-owner approval for a documented exception.\n- Coverage commands passed (`pnpm test:coverage` and `cargo llvm-cov ... --fail-under-lines 85`) or the change is docs-only.\n- Codacy: the final scan manifest; local before/after findings for every touched file; confirmation that every relevant local analyzer completed; and post-push dashboard findings after Codacy analyzed the exact pushed SHA. Confirm fewer findings in existing files (or zero stayed zero), exactly zero local and dashboard findings in every new code file, and zero findings attributable to added code in existing files at every severity.\n- Localization: any user-facing copy lives in `src/lib/locales/en.json`, `pnpm l10n:translate` was run, and `pnpm l10n:validate` passes. If no copy changed, say “Localization: no UI copy changes”.\n- PostHog: meaningful new user actions/events are instrumented with safe metadata; noisy/minor changes explicitly say “PostHog: no event needed because …”.\n- Refactoring: any files refactored to meet the CodeScene gate, or \"none needed\".\n- ADRs: any new/updated ADRs, or \"none\".\n- Docs: any updated docs (`docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, etc.), or \"none\".\n- Demo vault dirt checked: `git status --short -- demo-vault demo-vault-v2` is empty unless fixture changes are intentional.\n\n### ADRs & docs\n\nADRs live in `docs/adr/`. Create one in the same commit as the code. Preserve existing ADRs by creating a new one that supersedes them; editing an existing ADR requires an explicit repository-owner request. Use `/create-adr`. **When:** new dependency, storage strategy, platform target, core abstraction, cross-cutting pattern. **Not for:** bug fixes, styling, refactors.\n\nAfter any Tauri command, new component/hook, data model change, or new integration: update `docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, and/or `docs/GETTING-STARTED.md` in the same commit.\n\n---\n\n## 2. Product Rules\n\n### Demo vault hygiene (`demo-vault/`, `demo-vault-v2/`)\n\nDefault to `demo-vault-v2/` for testing.\n\n- Treat `demo-vault/` and `demo-vault-v2/` as disposable QA fixtures unless the task explicitly changes demo content.\n- If you create untracked notes, attachments, or other temporary files there for testing, delete them before the task is complete.\n- If you modify tracked demo-vault files only to test or QA behavior, revert those edits before the final commit.\n- Before declaring a task done, make sure `git status --short -- demo-vault demo-vault-v2` is empty unless demo fixture changes are part of the task.\n- If a fresh run starts and the only local dirt is inside `demo-vault/` or `demo-vault-v2/`, clean those paths first and continue. That case is recoverable QA residue, not a blocker.\n\n### User vault (`~/Laputa/`)\n\nDefault to `demo-vault-v2/`. If you must use `~/Laputa/` for testing:\n- Do not commit or push test notes to the remote vault unless the repository owner explicitly identifies that content as an intended deliverable.\n- **Delete all test notes from disk** when done — do not leave untitled or temporary notes on the filesystem. Run `cd ~/Laputa && git checkout -- . && git clean -fd` to restore the vault to its last committed state.\n- **Rationale:** test notes pollute the local vault over time, making it a collection of nonsensical untitled files. Keep the vault clean on disk as well as on the remote; any intended fixture exception requires explicit repository-owner approval.\n\n### UI components — mandatory rules\n\nUse shadcn/ui components for user-facing interactive elements instead of raw HTML form elements (`<input>`, `<select>`, `<button>`, native `<input type=\"date\">`, etc.). If no suitable shadcn or existing app component exists, stop and obtain explicit repository-owner design approval before introducing a raw control.\n\n| Need | Use |\n|---|---|\n| Text input | `Input` from shadcn/ui |\n| Dropdown/select | `Select` from shadcn/ui |\n| Date picker | `Calendar` + `Popover` from shadcn/ui (NOT native `<input type=\"date\">`) |\n| Button | `Button` from shadcn/ui |\n| Autocomplete/combobox | Reuse existing combobox components from the app (check `src/components/`) |\n| Wikilink picker | Reuse the wikilink autocomplete component already used in the editor and Properties panel |\n| Emoji picker | Reuse the emoji picker component already used for note/type icons |\n| Color picker | Reuse the color swatch picker used for type customization |\n| Toggle/switch | `Switch` or `ToggleGroup` from shadcn/ui |\n| Dialog/modal | `Dialog` from shadcn/ui |\n\n**Component search trigger:** when the requested interaction does not map to a component in the table above, search `src/components/` by the interaction name and ARIA role before building a component. **Visual language:** new UI must follow Tolaria's existing components and design tokens; a deliberate exception requires explicit repository-owner design approval recorded through the mandatory-rule exception protocol.\n\n---\n\n## 3. Reference\n\n### Release notes\n\nRelease notes live in `release-notes/<tag>.md` and are published to GitHub Releases and the public website.\n\n**Format:**\n- Every release note must start with frontmatter; exceptions require the mandatory-rule exception protocol:\n  ```markdown\n  ---\n  type: Note\n  _organized: true\n  ---\n  ```\n- After frontmatter, write the user-facing content (features, improvements, fixes)\n- Use clear headings: `## New Features`, `## Improvements`, `## Stability and Fixes`\n- Use emojis and **bold** feature names for scanability\n- Write for end users, not developers\n\n**Frontmatter handling:**\n- The vault displays release notes as organized `Note` types\n- GitHub Release bodies and website copies automatically strip frontmatter via `scripts/strip-frontmatter.mjs`\n- Never expose frontmatter in user-facing surfaces\n\n**Creating a new release note:**\n```bash\ncat > release-notes/<tag>.md << 'EOF'\n---\ntype: Note\n_organized: true\n---\n\n## New Features\n\n- 🎉 **Feature name** — Description.\n\n## Improvements\n\n- ✨ **Enhancement** — Description.\nEOF\n```\n\nThe frontmatter ensures release notes are already organized when the Tolaria repo is opened as a vault, avoiding inbox clutter.\n\n### macOS / Tauri gotchas\n\n- `Option+N` → special chars on macOS. Use `e.code` or `Cmd+N`\n- Tauri menu accelerators: `MenuItemBuilder::new(label).accelerator(\"CmdOrCtrl+1\")`\n- `app.set_menu()` replaces the ENTIRE menu bar — include all submenus\n- `mock-tauri.ts` silently swallows Tauri calls — not a substitute for native testing\n\n### QA scripts\n\n```bash\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh Tolaria\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/out.png\nbash ~/.openclaw/skills/tolaria-qa/scripts/shortcut.sh \"command\" \"s\"\n```\n\n### Diagrams\n\nPrefer Mermaid (`flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram-v2`). ASCII only for spatial wireframe layouts.\n\n## Stuck or blocked tasks\n\nWhenever an agent sets the `agent-blocked` label, moves a task back to Open, or otherwise leaves a task stuck, it must ALWAYS add a task comment explaining why: the exact blocker, the evidence (command/log path), and what decision or action would unblock it. Never leave a task blocked without that comment. Exception: a Linear outage after one retry requires the identical evidence in the final handoff and repository-owner approval under the mandatory-rule exception protocol.\n",".claude/commands/laputa-done.md":"# /laputa-done <task_id>\n\nMark a Laputa task as done: add completion comment, move to In Review, then self-dispatch the next task.\n\nRun this after Phase 1 (Playwright) and Phase 2 (native app QA) both pass **and `git push origin main` has succeeded**.\n\n⚠️ A task is NOT done until the push succeeds. If the push is blocked by the pre-push hook (clippy, tests, CodeScene, build):\n- Read the error\n- Fix it (never use `--no-verify`)\n- Commit the fix and push again\n- Repeat until push exits with code 0\n\n## Steps\n\n**1. Add completion comment to the task**\n\nSummarize what was done — this is the context Luca and Brian will read in Todoist:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"task_id\": \"$ARGUMENTS\",\n    \"content\": \"✅ Implementation complete.\\n\\n**What changed:** [brief summary of the implementation]\\n**ADR:** [if an ADR was created, reference it here; otherwise omit]\\n**Playwright:** all tests pass\\n**Native QA:** tested with pnpm tauri dev — [describe what was tested and what was observed]\"\n  }'\n```\n\n**2. Move task to In Review**\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/$ARGUMENTS/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjX33FF4Vj86M\"}'\n```\n\n**3. Pick the next task**\n\nRun `/laputa-next-task` to get the next task and start working on it immediately.\n\nIf there are no tasks, `/laputa-next-task` will wait 10 minutes and retry automatically. Do NOT exit — stay alive and let it loop.\n",".claude/commands/laputa-next-task.md":"# /laputa-next-task\n\nPick the next Laputa task from Todoist and move it to In Progress.\n\nPriority order: **To Rework** first, then **Open** (sorted by Todoist priority p1→p4).\n\n## Steps\n\n1. Fetch tasks from To Rework (`6g6QqvR9rRpvJWvv`), then Open (`6g3XjWR832hVHhCM`)\n2. **Sort by priority — this is mandatory.** Todoist returns tasks in arbitrary order. You must sort them yourself:\n   - Todoist priority field: `4` = p1 (urgent), `3` = p2, `2` = p3, `1` = p4\n   - Sort descending by `priority` field (4 first, 1 last)\n   - To Rework tasks always come before Open tasks regardless of priority\n   - **Never pick a p3/p4 task if a p1/p2 task exists in the same section**\n3. Take the first task from the sorted list\n4. Move it to In Progress (`6g3XjWjfmJFcGgHM`) via Todoist API:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/<task_id>/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjWjfmJFcGgHM\"}'\n```\n\n5. Add a \"started\" comment to the task:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"task_id\": \"<task_id>\", \"content\": \"🚀 Starting work. [Brief description of approach or what needs to be fixed]\"}'\n```\n\n6. Fetch the full task details (description, comments) from Todoist:\n\n```bash\ncurl -s \"https://api.todoist.com/api/v1/tasks/<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n\ncurl -s \"https://api.todoist.com/api/v1/comments?task_id=<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n```\n\n6. For To Rework tasks: read the ❌ QA failed comment — it tells you exactly what to fix\n7. Output: task ID, title, and full description so you can start working immediately\n\nIf no tasks are available in either section → wait 10 minutes and try again (loop forever):\n\n```bash\nwhile true; do\n  # ... check tasks ...\n  if no_tasks; then\n    sleep 600  # 10 minutes\n  else\n    break  # got a task, proceed\n  fi\ndone\n```\n\nDo NOT exit when there are no tasks. Keep looping until a task appears. This keeps Claude Code alive permanently — the watchdog is a safety net only, not the primary dispatcher.\n",".claude/commands/create-adr.md":"# Create Architecture Decision Record\n\nUse this command when you need to document an architectural decision made during a task.\n\nInspired by [adr-tools](https://github.com/npryce/adr-tools) (Nygard format), adapted for Laputa's frontmatter-based note format.\n\n## When to use this\n\nCreate an ADR when your work involves any of these:\n- Choosing a storage strategy (vault vs app settings vs database)\n- Adding or removing a major dependency\n- Supporting a new platform or target\n- Introducing or removing a core abstraction\n- Making a cross-cutting decision that affects how future code should be written\n\nDo NOT create ADRs for: bug fixes, UI styling, refactors that preserve behavior, or test additions.\n\n## Creating a new ADR\n\n### 1. Find the next ID\n\n```bash\nls docs/adr/*.md | grep -oP '\\d{4}' | sort -n | tail -1 | xargs -I{} printf '%04d\\n' $(({} + 1))\n```\n\nIf no files exist yet, start at `0001`.\n\n### 2. Create the file\n\nFilename: `docs/adr/NNNN-short-kebab-title.md`\n\nTemplate:\n\n```markdown\n---\ntype: ADR\nid: \"NNNN\"\ntitle: \"Short decision title\"\nstatus: active\ndate: YYYY-MM-DD\n---\n\n## Context\n\nThe issue motivating this decision, and any context that influences or constrains it.\n\n## Decision\n\n**The change we're proposing or have agreed to implement.** State it clearly in one or two sentences — bold so it stands out.\n\n## Options considered\n\n- **Option A** (chosen): brief description — pros / cons\n- **Option B**: brief description — pros / cons\n- **Option C**: brief description — pros / cons\n\n## Consequences\n\nWhat becomes easier or harder as a result?\nWhat risks does this introduce that will need to be mitigated?\nWhat would trigger re-evaluation of this decision?\n\n## Advice\n\n*(optional)* Input received before making this decision — who was consulted, what they said.\nOmit this section if the decision was made without external input.\n```\n\n### 3. Update the index\n\nAdd a row to `docs/adr/README.md`:\n\n```markdown\n| [NNNN](NNNN-short-kebab-title.md) | Title | active |\n```\n\n### 4. Include in the same commit as the feature\n\n```bash\ngit add docs/adr/NNNN-*.md docs/adr/README.md\n# fold into the feature commit — do not create a separate commit just for the ADR\n```\n\n---\n\n## Superseding an existing ADR\n\nEquivalent of `adr new -s <N>` from adr-tools — do this in two steps:\n\n### Step 1: Mark the old ADR as superseded\n\nEdit the existing file — add `superseded_by` and update `status`:\n\n```yaml\n---\ntype: ADR\nid: \"000N\"\ntitle: \"Old decision title\"\nstatus: superseded          # ← change from active\nsuperseded_by: \"NNNN\"       # ← add this\ndate: YYYY-MM-DD\n---\n```\n\n**Never edit the content sections** of an active ADR — only the status metadata.\n\n### Step 2: Create the new ADR\n\nFollow the steps above. In the **Context** section, reference the superseded ADR:\n\n```markdown\n## Context\n\nSupersedes [ADR-000N](000N-old-title.md).\n\n[explain why the old decision no longer holds]\n```\n\n### Step 3: Update the README index\n\nChange the old row's status to `superseded`, add the new row.\n\n---\n\n## Best practices (from adr-tools / Nygard)\n\n- **One decision per ADR** — if you find yourself writing \"and also\", split it\n- **Write Decision first** — if you can't state it in 1-2 sentences, the decision is too vague\n- **Context is the \"why now\"** — what forced this decision to be made today?\n- **Consequences should include negatives** — a one-sided ADR is a red flag\n- **Committed = immutable** — once pushed, the content doesn't change; only status metadata does\n- **If in doubt, create one** — cheaper to have an unnecessary ADR than to lose context\n- Date = today's date, `YYYY-MM-DD`\n","CLAUDE.md":"---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Claude Code compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n\n<!-- verity-memory:start -->\n## Project Memory\n\nThis project has a knowledge graph maintained at `.verity/memory/`. Before starting\nnon-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns\nthat may apply to the change you are about to make. Open specific node files via\nthe Read tool when the title or scope suggests relevance.\n\nThe graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are\nsuperseded — ignore them unless investigating history.\n\nKnowledge the organization's other repositories learned is mirrored outside the\nrepo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).\nEach claim says where it came from; one that is wrong here is demoted for\neveryone with `verity memory demote <id> --reason \"…\"`.\n\n## Quality gate: accepted risks\n\nWhen the Verity pre-commit/pre-push gate FAILs, fix the findings — that is the\ndefault. Use `verity waive <pattern-id> --file <path> --reason \"…\"` ONLY to relay\na risk a human has explicitly accepted: a named code-review finding, an ADR, or\nthe user saying so in this conversation. The --reason must cite that source.\n\nNever waive on your own judgment, to get past a block, or pre-emptively. A waive\nbinds to the file's current bytes and voids automatically when the file changes,\nand every waive is recorded in the run ledger. For a pattern-level false positive\nuse `verity feedback finding <run-id> <pattern-id> false_positive` instead.\n\n## Post-task reflection\n\nWhen a task is complete (you've created a PR, the user says \"done\" or \"ship it\",\nor the work is clearly finished), **draft the reflection yourself** — 1–3\nconcrete things worth remembering (a decision, a gotcha, or a pattern), each\ncited with the files / PR / commands / error-signatures it came from. Skip\nentirely if nothing non-obvious happened — that judgement is the ONLY filter,\nbecause nothing reviews the reflection before it lands.\n\nThen record it straight away. There is no confirm step, in any environment:\n\n```bash\nverity reflect --user-input - --kind <kind> <<'VERITY_EOF_<random>'\n<your draft>\nVERITY_EOF_<random>\n```\n\nThe draft goes through the quoted heredoc, never inside `\"…\"` on the command\nline, where a `\"` or `$(…)` in it would run in the shell. Replace `<random>`\nwith 8 random letters and digits that you pick for this call, the same at both\nends. Text written before you picked them cannot end the heredoc early.\n\nAdd `--confirmed` ONLY when the user authored or dictated the words. Without\nit the node is stored as `source: agent` — Verity thought this, nobody checked\nit. With it, `source: user` at full confidence — a person stands behind this.\nNever claim the second for your own draft, however good it is.\n\n**Name the files in the text.** Verity scopes the reflection to the paths it\ncites, and a reflection that names no file in this repo is never retrieved for\na later review — it is recorded and then invisible. The command says so when it\nhappens; `--file-globs '<path or glob>'` is the fix when the prose cannot carry\nthe paths.\n\nThen tell the user, in one line, what you recorded and where: the command\nprints the node id, the path under `.verity/memory/`, and a dashboard link.\nThey did not agree to it in advance, so say it happened — editing or deleting\nthat file is how they correct it.\n\n> Durable, hand-curated guidance goes in the preserve region below (it survives\n> regeneration) or anywhere OUTSIDE these markers. Everything else between the\n> markers is tool-owned and overwritten on each run.\n\n## Housekeeping Turns\n\nWhen a turn will be pure housekeeping — pulling, installing dependencies,\nrebasing, a formatting sweep you are not authoring — declare it BEFORE doing it:\n\n```bash\nverity ignore --turn --agent --reason \"pulling latest before starting\"\n```\n\nThis skips the review for that turn, which saves the turn Verity would\notherwise spend saying it had nothing to say. Use `--for 30m` instead of\n`--turn` when a single piece of housekeeping spans several turns.\n\n**It is a claim about the turn, not a way to silence review.** The declaration\nis checked against what the turn actually did: if anything is authored — by you,\nby a subagent, or by a shell command that can write files — it voids, the review\nruns anyway, and the broken declaration is reported. So declare housekeeping you\nare about to do, never work you have already done, and never as a way to get past\na finding. Declarations are budgeted per session and every one is recorded with\nits reason.\n\n<!-- verity-memory:preserve -->\n<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. -->\nVerity is set up only on some machines (currently the agent-claude box). If the `verity` CLI or\n`.verity/memory/` is missing, skip the Verity steps above; `AGENTS.md` stays the source of truth.\n<!-- /verity-memory:preserve -->\n<!-- verity-memory:end -->\n","docs/ARCHITECTURE.md":"# Architecture\n\n## Desktop Note Save Transport\n\n`persistContent` sends native desktop saves through `save_note_content_bytes`\nfor notes over 256 Ki characters, using raw UTF-8 content and a base64 UTF-8 `x-tolaria-note-path` header. The\ncommand rejects malformed requests and delegates to `save_note_content`, retaining\nactive-vault path validation and the existing off-thread atomic writer. This\nbounds the JSON note payload and avoids a full JSON copy of embedded image payloads; memory remains proportional\nto note length. Browser fixtures retain their JSON save API. See ADR 0278.\n\nTolaria is a personal knowledge and life management desktop app. It reads a vault of markdown files with YAML frontmatter and presents them in a four-panel UI inspired by Bear Notes.\n\n## Design Principles\n\n### Filesystem as the single source of truth\n\nThe vault is a folder of plain markdown files. The app never owns the data — it only reads and writes files. The cache, React state, and any in-memory representation are always derived from the filesystem and must be reconstructible by deleting them. When in doubt, the file on disk wins.\n\n### Convention over configuration\n\nTolaria is opinionated. Standard field names (`type:`, `status:`, `url:`, `Workspace:`, `belongs_to:`, `related_to:`, `has:`, `start_date:`, `end_date:`) have well-defined meanings and trigger specific UI behavior — without any setup. Relationship defaults are stored in snake_case on disk and humanized in the UI. This is not convention *instead of* configuration: users can override defaults via config files in their vault (e.g. `config/relations.md`, `config/semantic-properties.md`). But the defaults work out of the box, and most users never need to touch them.\n\nThis principle directly serves AI-readability: the more structure comes from shared conventions rather than per-user custom configurations, the easier it is for an AI agent to understand and navigate the vault correctly — without needing bespoke instructions for every setup.\n\n### Where to store state: vault vs. app settings\n\nWhen deciding where to persist a piece of data, ask: **\"Would the user want this to follow them across all their Tolaria installations — other devices, future platforms (tablet, web)?\"**\n\n| Follows the vault | Stays with the installation |\n|-------------------|-----------------------------|\n| Type icon, type color | Editor zoom level |\n| Pinned properties per type | API keys (OpenAI, Google) |\n| Sidebar label overrides | Auto-sync interval |\n| Property display order | Window size / position |\n| Per-note `_width` rich-editor width override | Default rich-editor note width |\n| Applicable vault or parent-repository `.gitignore` patterns | Whether this installation hides Gitignored files |\n| N/A | Whether this installation shows Git features |\n| N/A | Git executable provider (`native` vs WSL2 distribution) |\n| Per-vault All Notes note-list column overrides | All Notes PDF/image/unsupported file visibility |\n| N/A | Per-vault Git setup prompt opt-out |\n| Type `_sidebar_label` overrides | Whether this installation auto-pluralizes type labels |\n| N/A | Registered workspace labels, aliases, mount state, and default new-note destination |\n| N/A | Mobile AutoGit, appearance, interface scale and analytics consent |\n| Any user-visible customization of how content is organized or displayed | Any machine-specific or credential-type setting |\n\n**Rule:** If the information is about *how the content is structured or presented* and the user would expect it to be consistent wherever they open their vault, store it in the vault (frontmatter of the relevant note, using the `_field` underscore convention for system properties). If it's about *this specific installation of the app*, store it in app config JSON under `$XDG_CONFIG_HOME/com.tolaria.app/` (defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms), or in localStorage for renderer-only transient layout state. The native resolver keeps XDG as the preferred root when the current user can write the target; if that root is not writable, it uses the platform config root instead and reads that writable fallback before stale XDG files. See [ADR-0177](./adr/0177-writable-fallback-for-app-config.md).\n\nMobile installation preferences use the app-owned document configuration\ndirectory with validated pending-file publication and backup recovery (ADR 0212).\nManaged Git vaults retain the existing save/operation queues. Manual sync requires\nan explicit local commit; opt-in AutoGit schedules foreground idle checkpoints\nand synchronization without promising background execution.\n\nMobile Git merges validate synthesized frontmatter and saved-view/configuration\nblobs before publishing the proposed tree (ADR 0220). Invalid metadata leaves\nHEAD, index and working files untouched for explicit conflict resolution.\nGit sync has an independent operation mutex and uses short editor/write barriers\naround checkpoints and incoming checkout, not HTTP waits (ADR 0221). Temporary\nfailures retry with bounded backoff, with Expo network reconnection as a retry\nhint. Authentication and conflict failures require attention.\nInstallation-local sync history is keyed by managed vault ID and published with\nbackup recovery (ADR 0222). It records successful remote/provider operations\nseparately from local commits and pending edits, without storing user content.\nThe same ledger keeps installation-local, revisioned paths for pending note dots\n(ADR 0271). A successful checkpoint acknowledges only the captured revisions;\nedits made while transport is running remain pending. These hints never replace\nfresh Git/content comparisons or authorize checkout.\nNative conflict review shares local/remote/keep-both choices across Git and linked\nfolders (ADR 0223). Git retains both parents; coordinated folder publication\nretains private original snapshots and an interruption journal. Comparisons are\nversion-checked again before applying any choices.\n\nNew iOS folder selections retain a Files-provider bookmark alongside an offline\nworking copy (ADR 0217). Native, coordinated three-way reconciliation writes\nlocal edits back and imports external changes; divergent versions stop for\nresolution. This is independent of Git. Provider delivery, including iCloud's\ncross-device upload, remains the provider's responsibility. Existing copy-only\nvaults are never silently attached to an original folder.\n\nExamples:\n- ✅ Vault: `_pinned_properties` in a Type note (every device should show the same pinned properties)\n- ✅ Vault: `_icon: shapes` in a Type note (icon is part of the type's identity)\n- ✅ Vault: `_width: wide` in a note that already has frontmatter (per-note reading/editing preference)\n- ✅ App settings: `zoom: 1.3` (machine-specific preference)\n- ✅ App settings: `ui_language: \"zh-CN\"` (installation-specific UI language)\n- ✅ App settings: `note_width_mode: \"wide\"` (installation-specific default for notes without an override)\n- ✅ App settings: `date_display_format: \"friendly\"` (installation-specific date rendering preference)\n- ✅ App settings: `sidebar_type_pluralization_enabled: false` (installation-specific sidebar label preference)\n- ✅ App settings: `all_notes_show_images: true` (installation-specific All Notes file-category visibility)\n- ✅ App settings: `git_provider: \"wsl\"` plus `git_wsl_distro: \"Ubuntu\"` (machine-specific Git executable location)\n\n### No hardcoded exceptions\n\nNo field names, folder paths, or vault-specific values should be hardcoded in the application source code. What can be a convention should be a convention. What needs to be configurable should live in a file. Relationship fields are detected dynamically by checking whether values contain `[[wikilinks]]` — no hardcoded field name lists.\n\n### AI-first knowledge graph\n\nNotes are not just documents — they are nodes in a structured graph of people, projects, events, responsibilities, and ideas. Every design decision should ask: \"Does this make the knowledge graph easier for a human *and* an AI to navigate?\" Conventions that are legible to both are better than conventions that are legible only to one.\n\n### Three representations, one authority\n\nVault data exists in three forms simultaneously:\n1. **Filesystem** — the `.md` files on disk. This is the single source of truth.\n2. **Cache** — `~/.laputa/cache/<hash>.json`, an index for fast startup. Always reconstructible from the filesystem.\n3. **React state** — the in-memory `VaultEntry[]` during a session. Always derived from the cache or filesystem.\n\nThese must never diverge permanently. If they do, the filesystem wins and the cache/state are rebuilt.\n\n```mermaid\nflowchart LR\n    FS[\"🗂️ Filesystem\\n.md files on disk\\n(source of truth)\"]\n    Cache[\"⚡ Cache\\n~/.laputa/cache/\\n(fast startup index)\"]\n    RS[\"⚛️ React State\\nVaultEntry[]\\n(in-memory session)\"]\n\n    FS -->|\"scan_vault_cached()\"| Cache\n    Cache -->|\"useVaultLoader on load\"| RS\n    FS -->|\"reload_vault (full rescan)\"| RS\n    RS -.->|\"write via Tauri IPC first\"| FS\n\n    style FS fill:#d4edda,stroke:#28a745,color:#000\n    style Cache fill:#fff3cd,stroke:#ffc107,color:#000\n    style RS fill:#cce5ff,stroke:#004085,color:#000\n```\n\n#### Ownership rules\n\n| Layer | Owner | Writes to | Reads from |\n|-------|-------|-----------|------------|\n| Filesystem | Tauri Rust commands (`save_note_content`, `update_frontmatter`, etc.) | Disk | — |\n| Cache | `scan_vault_cached()` in `vault/cache.rs` | `~/.laputa/cache/` | Filesystem + git diff |\n| React state | `useVaultLoader` + `useEntryActions` + `useNoteActions` | In-memory `entries` | Cache (on load), filesystem (on reload) |\n\n#### Invariants\n\n1. **Disk-first writes**: All functions that change vault data must write to disk (via Tauri IPC) *before* updating React state. This ensures that if the disk write fails, React state remains consistent with what's actually on disk.\n2. **Optimistic UI with rollback**: Where responsiveness matters (e.g. `persistOptimistic` in `useNoteCreation`), state may update before disk confirmation — but a failure callback must revert the optimistic state.\n3. **No orphan state updates**: Never call `updateEntry()` before the corresponding `handleUpdateFrontmatter()` or `handleDeleteProperty()` has resolved. Type metadata actions in `useEntryActions` follow this rule — create the missing type document if needed, write frontmatter first, then update state. If missing type creation collides with an existing note filename, the action stops after the existing creation toast instead of applying orphaned state.\n4. **Recovery via reload**: If state ever diverges from disk (crash, external edit, race condition), `Reload Vault` (Cmd+K → \"Reload Vault\") does a full filesystem rescan via the `reload_vault` Tauri command, atomically replacing the cache and all React state. The `reload_vault_entry` command can re-read a single file.\n5. **Cache is disposable and crash-safe**: A full reload keeps the last valid snapshot readable while it builds a replacement, then swaps the new cache into place only after the scan completes. A crash during refresh therefore cannot turn the next startup into a cache-miss scan. The cache remains derived from the filesystem and can always be deleted and rebuilt.\n6. **Visibility filters are command-boundary concerns**: Gitignored-content visibility is applied after scanning/caching, before entries, folders, or search results reach React. The cache remains complete so toggling the setting can show ignored content again without rebuilding a different cache shape. Large folder filtering runs on the blocking Tokio pool and drains `git check-ignore` output while feeding stdin so broad ignore matches cannot freeze the native UI thread.\n\n#### External Change Detection\n\nEvery full App window registers its active vault roots through `start_vault_watcher` / `stop_vault_watcher` (`src-tauri/src/vault_watcher.rs`, backed by Rust `notify`). The backend reuses one native watcher per shared root, tracks owning window labels, and removes a watcher only after its final owner unregisters. Watchers emit `vault-changed` events for content paths and ignore churn from `.git/`, `node_modules/`, temp files, and `.tolaria-rename-txn`. `useVaultWatcher` batches those events and suppresses recent app-owned saves. The main window sends remaining paths through `refreshPulledVaultState()` so folders, saved views, note-list state, and clean active-editor content refresh under the ADR-0135 unsaved-edit rules. Secondary note windows reload known changed entries individually, preserving the full graph while avoiding full scans; deleted, moved, unknown, or otherwise non-reloadable paths fall back to the shared full reconciler. A same-path replacement keeps the active tab mounted, and a frontmatter-only replacement keeps the current BlockNote document, caret, focus, and scroll position. Content changes still replace the clean editor document, while a true move or deletion clears the obsolete tab; when a document replacement starts from a focused editor, the app dispatches a post-replacement editor focus request. `useVaultLoader.isReloading` drives the status-bar reload spinner for both manual and full watcher-triggered reloads.\n\nWindow focus also requests Git-based external rename detection for the active vault. The shared `commands::blocking` boundary schedules synchronous command work on Tokio's blocking pool and converts join failures to string errors, retaining contextual labels where needed. The `detect_renames` Tauri command uses this boundary for its filesystem and Git subprocess work so a slow repository cannot stall the native event loop while the user resumes typing.\n\n#### Progressive Vault Loading\n\nVault opening waits for the persisted vault registry before selecting a path, so it never scans the legacy default vault on the way to the active workspace. On a warm start, `read_vault_snapshot` deserializes a version- and path-valid active-vault cache without Git, per-entry filesystem checks, sorting, or cache replacement; command-boundary visibility filtering can remove Gitignored entries without discarding that snapshot. React installs the provisional graph and clears `isLoading`, then `list_vault` reconciles Git changes and missing files in the background. Invalid or missing snapshots and forced reloads use the authoritative scan path. Clean same-commit reconciliation avoids both the full Git-date scan and an identical cache rewrite. Folders and saved views load independently; secondary workspaces start only after the active graph is usable and hydrate one at a time. Nested mounts derive their entries from a loaded ancestor instead of scanning the same subtree again. BlockNote and editor-only styles remain outside the initial shell chunk and prewarm after its first commit; selecting a note requests them immediately.\n\nStartup performance is measured end to end with ordered, path-free milestones spanning renderer initialization, persisted settings, vault selection, snapshot hydration, interactivity, editor loading, and background reconciliation. Each milestone records both renderer-relative and native-process elapsed time; `TOLARIA_STARTUP_TRACE=1` writes the same JSON events to stderr for repeatable native measurements without adding UI. Git repository probing runs on the blocking Tokio pool, and missing Git-related environment variables are resolved with one login-shell invocation rather than one process per variable. See [ADR-0166](./adr/0166-snapshot-first-progressive-vault-startup.md) and [ADR-0170](./adr/0170-measurable-crash-safe-startup.md).\n\nLarge-vault reproduction and keyboard QA steps live in [LARGE-VAULT-LOADING-QA.md](./LARGE-VAULT-LOADING-QA.md).\n\n#### Mounted Workspaces\n\nThe registered vault list can act as a mounted-workspace set. `useVaultSwitcher` persists each workspace's installation-local identity (`label`, stable `alias`, color, mount flag) and the default destination for newly created notes in `vaults.json` under Tolaria's app config directory (`$XDG_CONFIG_HOME/com.tolaria.app/`, defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms). If the native app cannot write that XDG target, it falls back to the platform config directory for both the write and the first subsequent read. `useVaultLoader` scans every available mounted workspace and annotates each `VaultEntry` with provenance before React consumes the combined graph. The default workspace is the write target for new notes and Type documents; it is not the only active vault when multiple workspaces are enabled.\n\nExternal MCP clients share that registry boundary. `attach_vault` registers an existing canonical directory without initializing Git, while `clone_vault` uses non-interactive system Git in a temporary sibling and publishes the destination only after clone success. Both preserve the current active vault, add the new root to the calling MCP process immediately, and broadcast a registry-change intent so running renderers reload mounted-workspace metadata without relaunching or switching focus. See [ADR-0175](./adr/0175-mcp-vault-lifecycle-uses-app-registry.md).\n\nThe status-bar vault menu can open a registered vault as an independent desktop application instance. `VaultMenu` owns rendering, while `vaultMenuInteractions`, `vaultMenuActions`, and `vaultMenuTrigger` isolate mounted-workspace behavior, action construction, and trigger styling. Ordinary launches still register the single-instance plugin, while an explicit vault-instance launch bypasses it and scopes that process's active vault without changing the shared registry's ordinary startup selection. Packaged macOS builds use Launch Services `open -n` so each vault receives its own Dock tile and Command-Tab entry. `src/shared/workspaceColorContract.json` is the cross-runtime source for renderer picker keys, launch validation, persisted vault-color normalization, and light/dark native icon tints. Each process's AppKit icon is generated from the bundled Tolaria icon by recoloring its droplet with the launched or active vault's declared native tint; renderer-only `pink` and `gray` colors explicitly fall back to the contract's default blue tint. See [ADR-0171](./adr/0171-separate-vault-application-instances.md).\n\nVault item deep links use the registered vault list as their resolver namespace. `src/utils/deepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from workspace aliases, labels, and paths, appending a short stable hash when generated slugs would collide. `useDeepLinks` validates incoming links, broadcasts cross-window route requests so an already-open matching vault instance can claim and focus the link, then falls back to switching the receiving process's vault when no matching window answers. The owning window reloads the vault index once for recently changed files and opens the matching `VaultEntry` through the normal note-selection path.\n\nSaved Views participate in that mounted graph as source-scoped chrome. `useVaultLoader` loads view definitions from every mounted vault, annotates each `ViewFile` with its owning `rootPath` and workspace identity, and keeps sidebar selection/persistence keyed by `(rootPath, filename)` so same-named view files from different vaults stay independent.\n\nGit surfaces resolve repository paths explicitly. `useGitRepositories` derives the active repository set from the mounted available workspaces, keeps separate selected repositories for Changes, Pulse/history, and manual commits, and exposes the combined modified-file count for status/commands. Native Git capability checks treat a mounted workspace as Git-backed when it is inside any Git work tree, so included vault folders can reuse a parent repository without creating embedded `.git` directories. Rust resolves each vault to a `GitWorkspace` (`vault_root`, provider-discovered `git_root`, and repository-relative `vault_pathspec`). Vault-scoped status, diff, history, dates, pulse, conflict, and commit commands execute at the Git root with that pathspec, then map Git output back to vault-relative note paths. Branches, remotes, fetch, pull, push, merge, and rebase remain repository-wide; the settings row exposes the resolved root and parent-repository sync feedback identifies that wider scope. App-created commits use path-limited staging plus `git commit --only` so sibling staged and unstaged work is preserved. Mounted folders outside Git return no changes/no remote for aggregate probes instead of failing workspace-wide sync. AutoGit checkpoints iterate that repository set, while manual commit, history, diff, and discard operations use the selected surface or the note's workspace provenance. See [ADR-0161](./adr/0161-parent-git-workspace-context.md).\n\nGit command launch is also installation-local. The Rust Git module resolves a `GitLaunchConfig` from app settings before spawning Git, defaulting to native `git` while allowing Windows installations to explicitly select WSL2 Git and a distribution. WSL-selected launches go through `wsl.exe --exec git` and translate Windows/WSL UNC vault paths before passing repository paths across the command boundary; Tolaria never silently switches providers based on detection alone.\n\nRenderer git file workflows stay behind `useGitFileWorkflows`. The hook resolves per-note repository paths, queues editor diff requests, opens Pulse history entries including deleted-file previews, and keeps discard/reload handling close to the selected Git surface while `App.tsx` only wires the resulting callbacks into `NoteList`, `PulseView`, and `Editor`. `useInitialPulseLoad` separately owns cancellable Pulse initial, refresh-key, and retry requests; pagination remains in `PulseView`. Note history reloads only when its note, visibility, or explicit refresh generation changes; changing renderer callback identities during an editor-state update does not rerun `git log`, and the native history command executes that blocking process on Tokio's blocking pool.\n\nCross-workspace note reads and writes keep the disk-first invariant. When an absolute note path is saved or read without an explicit `vaultPath`, the Tauri boundary resolves the deepest registered vault root that contains the path and validates against that root before touching disk. This lets an editor tab opened from a mounted workspace save back to its source repository while preserving the same path-escape protections as active-vault operations.\n\n#### Note Opening Fast Path\n\nNote opening uses bounded in-memory fast paths for raw content and parsed editor blocks. `useTabManagement` owns the markdown/text prefetch cache and treats every cached value as a performance hint only: identity-matched entries (`modifiedAt` + `fileSize`) can be reused immediately, while identity-missing or identity-mismatched cached text is checked with `validate_note_content`, which compares the cached text with the current file bytes inside the validated vault boundary. If validation fails, Tolaria discards the cached entry and reads fresh disk content before swapping the editor.\n\nThe note list opportunistically preloads visible and adjacent markdown/text entries after a short delay. When a large warmed Markdown note resolves, `useEditorTabSwap` may parse it into a bounded parsed-block cache only after foreground editor work has been idle and the rich editor is mounted. On foreground open, block resolution checks the hot tab cache, exact-source parsed-block cache, blank/H1 fast path, and a direct Markdown-to-block parser for large common Markdown before falling back to BlockNote's parser. The direct parser runs in a module worker when available and falls back synchronously only for tests or runtimes without workers. It is conservative: unsupported Markdown constructs return to the BlockNote path rather than changing semantics. Large resolved block sets mount progressively: the editor is locked, the first chunk is applied immediately, remaining chunks append across animation frames, and the same generation/source-content token can abort the partial mount before it is committed. Progressive chunk boundaries expand through contiguous numbered-list items so BlockNote receives each ordered-list run in one mutation and preserves continuous numbering. Every programmatic document replacement, including progressive chunks and fallback recovery, is excluded from ProseMirror history so note navigation cannot retain previous document graphs or displace user typing from undo. Ordinary BlockNote block wrappers stay fully measurable after mount so hover side menus, slash dialogs, and document-end interactions do not inherit browser lazy-height scroll jumps. Parsed blocks are keyed by vault, path, and exact source content; every async swap carries a generation/source-content token so stale conversion results cannot overwrite newer file content or dirty editor state. The editor never renders a preview surface that later morphs into BlockNote. Development builds log `editorBlockResolve`, `editorBlockApply`, and `parsedBlockPreload` timings for large or slow note opens. See [ADR-0105](./adr/0105-editor-correctness-and-responsiveness-contract.md).\n\nRich-editor Markdown boundaries stay on the editor fast path while keeping one owner for import, installation, and export. `src/utils/richEditorMarkdown.ts` installs the direct serializer on real BlockNote editors, preprocesses Markdown before parsing, injects Tolaria schema blocks after parsing, and serializes body/document Markdown for saves, raw-mode entry, tab swaps, and position snapshots. Its lower-level direct serializer (`src/utils/blockNoteDirectMarkdown.ts`) serializes common Tolaria block shapes to Markdown with a per-editor weak cache keyed by stable block object identity and list context. Durable Markdown bridges for wikilinks, linked inline-code labels, math, highlights, files, Mermaid, tldraw, sandboxed HTML, blockquote callouts, and literal path-template backslashes stay behind `richEditorMarkdown.ts`; linked code and vulnerable literal backslashes are protected by Markdown-inert placeholders during import and restored before editing, while the direct serializer keeps safe identifier underscores, Windows-style separators, and line-ending-adjacent formatting delimiters stable. The bridge helpers preserve unchanged block objects so repeated debounced saves can reuse direct Markdown projections. Highlights use portable Bear-compatible prefixes for custom colors (`==🔴red==`, `==🔵blue==`, `==🟣purple==`, and `==🟢green==`) while the prefix-free `==text==` form remains the default yellow. Callouts keep marker metadata in block props but retain their body as editable inline content, and their mixed serializer restores portable `> [!type]` syntax. Stable active-tab rerenders trust the exact-source block cache before doing any full-document Markdown comparison, so unrelated React state changes do not serialize large notes. Unsupported block shapes fall back to BlockNote's lossy serializer, and development builds log `richEditorSerialize` timing/cache/fallback metrics plus rare `editorStabilityCheck` fallback comparisons for large or slow serializations.\n\nRich-editor typing keeps BlockNote's per-transaction work minimal. Tolaria creates BlockNote with animation bookkeeping disabled so the `previousBlockType` extension, which scans the old and new ProseMirror documents on every edit, is not installed. Collapsed-heading rendering attaches its mutation observer and `editor.onChange` subscription only while at least one heading/list section is collapsed, then detaches when the final section expands. Heading folds follow the document outline through divider blocks and stop at the next same-or-higher heading. Development builds wrap ProseMirror dispatch once per editor view and log `richEditorDispatch` timings for large or slow transactions, giving QA a direct signal for edit-time stutter instead of only note-open speed. The pinned `prosemirror-view` dependency normalizes a stale child boundary to the live parent end during DOM-selection synchronization so editor teardown or remount cannot dereference a child removed mid-scan; see [ADR-0183](./adr/0183-normalize-stale-prosemirror-selection-boundaries.md).\n\nCore perceived latency is protected by a synthetic production-browser benchmark. `pnpm perf` injects deterministic 500-entry startup/note fixtures and a 2,000-entry virtualized-list fixture, discards a warmup, then aggregates five measured runs. Startup phases publish standard `tolaria:<phase>` User Timing marks; note scenarios combine visible editor/content milestones with opt-in note/editor trace durations; list scrolling waits through the destination paint. `.editor-performance-thresholds.json` stores ratcheted median budgets and p90 diagnostics. CircleCI runs the gate in a dedicated browser job, while native `TOLARIA_STARTUP_TRACE=1` remains the richer host-level diagnostic tier. See `docs/PERFORMANCE.md` for the instrumentation audit and baseline policy.\n\n## Tech Stack\n\n| Layer | Technology | Version |\n|-------|-----------|---------|\n| Desktop shell | Tauri v2 | 2.10.0 |\n| Frontend | React + TypeScript | React 19, TS 5.9 |\n| Mobile shell | Expo React Native | Expo 54, React Native 0.81 |\n| Editor | BlockNote | 0.46.2 |\n| Editor render extensions | @tiptap/pm | ProseMirror decorations for rich-editor node presentation |\n| Code block highlighting | @blocknote/code-block | 0.46.2 |\n| Additional code grammars | @shikijs/langs | 3.23.0 |\n| Diagram rendering | Mermaid | 11.14.0 |\n| Whiteboard rendering | tldraw | 4.5.10 |\n| Raw editor | CodeMirror 6 + official language packages | Markdown, standalone/fenced HTML, YAML, JSON, Python, SQL, JS/TS |\n| Spreadsheet editor | IronCalc workbook + WASM | 0.5.x |\n| Styling | Tailwind CSS v4 + CSS variables | 4.1.18 |\n| UI primitives | Radix UI + shadcn/ui | - |\n| Mobile UI primitives | Nativewind + React Native Reusables via Tolaria-owned wrappers | Nativewind 4, RNR registry primitives |\n| Icons | Phosphor Icons | Full catalog; per-icon modules load on demand |\n| Build | Vite | 7.3.1 |\n| Backend language | Rust (edition 2021) | 1.77.2 |\n| Frontmatter parsing | gray_matter | 0.2 |\n| Filesystem watcher | notify | 6.1 |\n| AI (workspace) | CLI agent adapters (Claude Code + Codex + GitHub Copilot + OpenCode + Pi + Antigravity + Kiro + Hermes Agent) plus configured local/API model targets | - |\n| Search | Keyword (walkdir-based file scan) | - |\n| Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync |\n| MCP | @modelcontextprotocol/sdk | 1.0 |\n| Tests | Vitest (unit), Playwright (E2E/smoke), cargo test (Rust) | - |\n| Package manager | pnpm | - |\n\nThe icon registry derives its stable kebab-case catalog from the Phosphor CSR\nmodule filenames at build time. Each icon remains a separate Vite dynamic\nimport, so resolving an uncommon vault icon loads only that SVG component.\nThe type customization grid renders the catalog in progressive batches while\npreserving search across every registered icon. See [ADR-0174](./adr/0174-lazy-full-phosphor-icon-catalog.md).\n\n### Mobile App Foundation\n\nStandalone iOS editor text saves use the existing workspace module's asynchronous\natomic writer (ADR 0203). A private, versioned recovery record is published before\nreplacement; restart recovery precedes indexing, reads, and Git synchronization.\nUnexpected destination changes retain the pending draft and fail closed. This\nprotects published text saves, not unsent keystrokes. Structural entry operations\nuse the same recovery boundary: exclusive rename for moves, private quarantine\nbefore deletion cleanup, and UUID completion receipts for safe queue retries\n(ADR 0204). Text and entry retries consult completion receipts before capturing\nnew preconditions, so replay cannot overwrite later edits or delete recreated\nentries (ADR 0206). A move plus its subsequent backlink rewrites is not yet one transaction.\nExpo Go and Android still require their own equivalent persistence implementation.\n\nThe native text engine also supports a version-two journal containing an entire\ntext-only batch (ADR 0207). It preflights all prior digests and retains the plan\nwhen an external edit prevents replay. Single-save journals and receipts remain\nbackward readable. The repository routes entirely text-based multi-write plans\nthrough the native batch capability, coalescing repeated paths to their final\ncontent. Single saves, mixed structural plans, and runtimes without that\ncapability retain their existing individual-operation queue boundaries.\n\nThe same iOS module exposes an optional, single-call Git metadata reader anchored\nto the app sandbox. It returns fresh stat fields without caching file state;\nExpo continues to own byte IO. Rapid-edit staging and checkout recovery remain\nunchanged (ADR 0205).\n\nMobile saved-view loading uses `mobileViewYaml.ts` and the `yaml` parser with\nbounded input, alias expansion and filter depth (ADR 0210). Valid desktop YAML\nindentation and flow collections retain their filters; malformed definitions are\nrejected instead of becoming all-note views. Evaluation and serialization remain\nin `mobileSavedViews.ts`. Sidebar type discovery has no arbitrary count cap and\nuses the desktop pluralization helper, while explicit type labels take priority.\n\n`MobilePanelSafeArea` owns each tablet pane's top inset, background and divider.\nThere is no workspace footer: Settings lives at the sidebar bottom. The phone\nshell colors its top inset from the active panel. Panes and their dividers reach\nthe bottom edge; bottom controls and scrollable inspector content own their\nhome-indicator clearance without leaving a separate band below the workspace.\n`MobileGitHubRepositories` uses a bounded, searchable FlatList picker, and\n`MobileDeviceCode` copies authorization codes without routing secrets through the\ndiagnostic clipboard helper. Inspector relationship groups expose their own add\naction, and native option rows use explicit styles on the RNR button primitive.\n\nThe vault catalog remembers local imports alongside Git working copies (ADR\n0216). Each new local import has its own app-owned directory; the old single\nimport is registered in place once. One mutually exclusive active pointer is\nrestored before workspace loading. Forgetting changes only the catalog, never\nthe vault's files. Settings reuses the vault manager's GitHub account controller\nand hands off vault operations through the existing editor-save barrier.\n\nLazy note hydration reparses only the loaded target, resolves its relationships\nagainst the full note pool, and retains unrelated note object identities. It\ndoes not emit filesystem writes or change modified timestamps. The native\nread-only benchmark measures hydration separately from initial indexing and\ntype filtering; these are data-path timings, not screen-transition timings.\n\nStorage verification can also run against an optimized local binary using the\nexplicit `TOLARIA_QA` Swift condition (ADR 0208). A read-only native capability\nenables the existing loopback-only probes without Metro or development JavaScript.\nOrdinary Release excludes that native API. Git QA records JavaScript timer delay\nseparately from clone/push/pull duration; neither measure represents native frame\nrate or a physical-device performance budget.\n\nPhone layout uses `useMobileDeviceLayout` instead of an orientation-sensitive\nwindow-width breakpoint. Its actual panels occupy fixed slots on one retained\nrail; `usePhonePanelRail` drives that rail through RNGH and Reanimated on the UI\nthread. Incoming panes are not duplicate previews, and settling does not reset\nthe offset or start a second entrance animation. `usePhoneViewportWidth` accounts\nfor the outer safe area once, so landscape panes and edge gestures use the real\ncontent viewport. Inactive content is excluded from input and accessibility;\nthe editor is created on its first visit and retained thereafter. See ADR-0224\nand ADR-0244. Native acceptance covers cancellation, draft/focus continuity,\nscroll position, rotation, note-row actions and corresponding iPad behavior.\n\nThe mobile app lives in `apps/mobile` as a separate Expo React Native workspace package. It is intentionally not a port of the desktop React DOM component tree. Desktop components, CSS variables, and panel semantics are treated as the reference contract, while mobile uses native primitives and fixture-driven screens. Distribution builds use Expo SDK 57 with the supported `expo-build-properties` scene-lifecycle opt-in for Xcode 27/iOS 27 compatibility (ADR 0246). Prebuild generates Expo's scene manifest and window owner; the temporary Expo Go 54 preview constraint is superseded. Native dependencies and scene support require a new signed build and OTA fingerprint, not a JavaScript-only update.\n\nThe first mobile surface is a UI lab with real workspace semantics behind a fast fixture default. `apps/mobile/src/components/ui` owns RNR-derived shadcn-style primitives such as text, buttons, and badges, while `apps/mobile/src/ui` owns Tolaria-specific wrappers such as icon buttons, panels, toolbars, list rows, chips, and property rows. Workspace components live under `apps/mobile/src/components/workspace` and consume `MobileWorkspaceSnapshot` from `apps/mobile/src/workspace/mobileWorkspaceModel.ts`. Tablet panel gestures use one workspace-level horizontal responder and Reanimated offsets. The sidebar and note list remain mounted as one strip with desktop-token snap points at 0, -260, and -600 pixels, so a drag begun anywhere in workspace content can directly hide or reveal both panels without lifting. Only a hidden Properties panel reserves a 32-pixel right-edge reveal gesture; the editor toolbar exposes the equivalent explicit toggle. The native panel-gesture QA maps React Native accessibility frames onto the rotated Simulator window, injects sampled real drags from content and the screen edge, and verifies each settled panel state through stable test identifiers. The default repository remains fixture-backed for screenshot speed, while the Playwright QA harness can inject a real read-only local-vault snapshot through `tolaria:mobile-workspace-snapshot` when `MOBILE_QA_VAULT_PATH` is set. Explicit `source=native-vault` mode uses `expo-file-system` through `apps/mobile/src/workspace/expoWorkspaceFileSystem.ts`, reads an Expo-accessible vault directory, and persists reducer write plans back to markdown files and directories. On iOS, selecting a vault imports it atomically into the app-managed `Documents/Tolaria Vault` directory because document-picker access to arbitrary external folders cannot be reacquired after process termination; the custom `TolariaWorkspaceAccess` Expo module restores that managed local copy on cold launch and the app exposes Documents through Files. For launch performance, that module enumerates the managed vault off the JavaScript thread and returns one serialized file index to the existing pure snapshot builder, avoiding thousands of per-file React Native bridge calls; editor hydration and reducer write plans continue to use the filesystem repository rather than treating the launch index as a second persistence model. `apps/mobile/src/workspace/localVaultSnapshot.ts` keeps scanning as a pure files-plus-folder-paths transform: it parses Tolaria frontmatter conventions, preserves explicit empty folder paths for the sidebar, derives type colors from Type documents, caps rendered note rows for fast UI work, and is shared by host-vault QA and native-vault mode.\n\nTablet editing is reducer-owned and repository-persisted. `apps/mobile/src/workspace/mobileWorkspaceEditing.ts` applies note creation, title/body edits, frontmatter scalar property changes, relationship add/remove, archive, favorite, folder create/rename/delete, saved-view definition changes, primary note-list display-property config changes, and Type definition actions against the current `MobileWorkspaceSnapshot`. Note folder moves and explicit filename renames emit `moveNote` file-write plans plus only the `saveNote` writes needed for backlink rewrites, while folder edits update explicit folder paths, retarget notes in renamed subtrees, rewrite affected path-based wikilinks, and emit directory write plans instead of modeling folder renames as note delete/save loops. Saved-view edits persist desktop YAML fields including filters, `order`, and `listPropertiesDisplay`, so mobile view ordering and saved-view note-list column choices use the same file contract as desktop. Primary All Notes and Inbox display-property choices emit `saveVaultConfig` plans and are stored outside the vault file list by the repository adapter, matching the desktop vault-path-local config contract. Type document create/delete/update/reorder logic lives in `apps/mobile/src/workspace/mobileWorkspaceTypeEditing.ts` and persists the Type markdown document frontmatter for sidebar label, visibility, color, sort, note-list display properties, and instance schema/default fields through `apps/mobile/src/workspace/mobileTypeDefinitions.ts`; `apps/mobile/src/workspace/mobileTypeDefinitionSchema.ts` owns the form conversion between custom scalar properties, wikilink relationship fields, and blank built-in relationship placeholders. `apps/mobile/src/screens/tabletWorkspaceHistory.ts` records the mobile command history at the snapshot boundary: content/frontmatter edits replay as content writes, structural note, folder, saved-view, and Type-definition changes replay through exact restore edits, and `apps/mobile/src/screens/tabletWorkspacePathHistory.ts` routes note moves, note filename renames, and folder subtree renames back through inverse reducer commands so undo/redo preserves filenames, ordering, raw Markdown, backlink rewrites, and persisted write paths. `apps/mobile/src/workspace/mobileNeighborhood.ts` mirrors the desktop Neighborhood note-list contract for mobile snapshots by pinning the source note, grouping Type instances, direct relationships, inverse relationships, and Backlinks with the same desktop group labels, and preserving duplicate notes across groups. The reducer preserves frontmatter while the markdown editor edits body content, derives snippets/blocks/link targets back from Markdown, resolves wikilink relationship targets against the snapshot, and returns write plans. The source editor and web WYSIWYG fallback keep keystrokes in a local draft and flush through that reducer after a short idle window or when editing unmounts, matching the desktop edit-buffer boundary instead of rebuilding the workspace graph for every character. Mobile note snapshots preserve frontmatter aliases so wikilink resolution and editor autocomplete can match desktop identity candidates. The repository boundary decides whether those writes are no-ops for fixtures, host-injected QA logs, installation-local config writes, or native Expo filesystem writes. Visual components do not import filesystem APIs directly.\n\nThe native mobile WYSIWYG editor is wrapped behind `MobileWysiwygMarkdownEditor`. It registers Tolaria-owned TenTap bridge extensions only at that boundary and passes a generated Tolaria TenTap WebView source through `customSource`, so custom Tiptap nodes exist inside the editor WebView instead of only in React Native. `MobileWysiwygContentBridge.ts` hydrates programmatic document content without emitting user-edit updates, and the React Native lifecycle keeps persistence gated until an explicit touch reaches the editor surface; lazy note hydration therefore cannot be mistaken for a user edit, while the first intentional keystroke still saves normally. `MobileWysiwygMathBridge.ts` adds the `mathInline` and `mathBlock` nodes required for desktop math parity and renders both through the mobile KaTeX MathML helper. `MobileWysiwygTentapWebEditor.tsx` plus `apps/mobile/scripts/build-wysiwyg-editor-html.mjs` own the rebuildable WebView bundle. `mobileInlineMath.ts`, `mobileDisplayMath.ts`, `mobileDocumentContent.ts`, and `mobileWysiwygInputTransforms.ts` keep parsing, hydration, structural input transforms, and Markdown serialization pure and testable. Native WYSIWYG QA probes live beside the mobile editor boundary and are opened through Expo deep links; external links now have a dedicated simulator proof that applies and removes a TenTap link command, then verifies the serialized Markdown output rather than relying only on model tests.\n\nMobile slash commands and inline Find/Replace use that same TenTap boundary. Their ProseMirror plugins own query matching, decorations, keyboard selection and undoable document changes inside the WebView; narrow bridge messages expose state and actions to native RNR controls. Find uses the desktop's pure matching and safe-regex helpers without importing desktop UI. The mounted editor registers Find/Replace with the existing editor-command registry, so keyboard shortcuts and overflow actions reach the inline bar instead of a workspace modal. Source-mode Find uses the same bar and overlays its active match in the existing Markdown syntax layer, retaining YAML frontmatter. Tests mount the actual TenTap web runtime to avoid mixing incompatible ProseMirror instances, and native XCTest checks touch targets, inline placement and serialized Markdown.\n\nMobile note deep-link copy mirrors the desktop URL contract without importing desktop React DOM modules. `apps/mobile/src/workspace/mobileDeepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from the mobile snapshot source and selected note path, while `apps/mobile/src/workspace/mobileClipboard.ts` writes through Expo Clipboard. The action is exposed through the tablet controller/action-sheet callback boundary so visual rows remain platform-agnostic.\n\nMobile note PDF export follows that same boundary. `apps/mobile/src/workspace/mobilePdfExport.ts` strips YAML frontmatter through the mobile document-content helper, renders the note body to Tolaria-styled HTML, and uses `expo-print` plus `expo-sharing` only when running in the native Expo app. Browser-based UI lab checks record deterministic export attempts instead of opening a print dialog, and the generated PDF is treated as a temporary share artifact rather than a new vault file.\n\nMobile attachment import and opening also follow the controller/editor callback boundary. `apps/mobile/src/workspace/mobileAttachmentImport.native.ts` uses `expo-document-picker` plus the Expo FileSystem object API to copy selected files into the active vault's `attachments/` directory, while `mobileAttachments.ts` owns safe filenames and portable Markdown image/link serialization. `apps/mobile/src/workspace/mobileAttachmentUris.ts` is the native-boundary adapter that resolves portable `attachments/...` Markdown targets to Expo file URIs for TenTap/link opening, then converts vault-local file URIs back to portable Markdown before save. Source and WYSIWYG editor surfaces receive importer/open-link callbacks and attachment payloads instead of importing native picker, sharing, or filesystem modules directly.\n\nThe mobile command palette mirrors the desktop command boundary without importing desktop React hooks. `apps/mobile/src/workspace/mobileCommandPalette.ts` builds enabled mobile command rows from existing workspace callbacks, preserving shared IDs from `src/shared/appCommandManifest.json` when a command is manifest-backed and using desktop dynamic command-palette IDs for rows such as note retargeting, file utility actions, and note-width changes. Selection-sensitive editor actions such as paste-without-formatting are registered by the mounted source or native WYSIWYG editor through `apps/mobile/src/workspace/mobileEditorCommands.ts`, so tablet and phone palettes can dispatch the active editor command without importing editor internals. `apps/mobile/src/components/workspace/MobileCommandPalette.tsx` owns only the native searchable/grouped surface; create/search/navigation/note/file/view/settings execution remains in the reducer/controller callbacks. Hardware keyboard commands are exposed through the tracked `TolariaKeyCommands` Expo module: its React delegate handler supplies the root UIKit controller and registers priority `UIKeyCommand`s, then forwards normalized events to the same JavaScript router used by DOM keyboard QA. The root controller accepts first-responder status when no text input owns focus. JavaScript retains one subscription across renders and reads current handlers through a ref. Cmd+1/2/3 and Cmd+Shift+I use the existing tablet panel controller. Unmodified arrows belong to the focused native text input, the open command palette, or the visible note list, in that order; modal/hidden lists cannot consume them. The palette explicitly registers its lifetime with the native module, receives arrows/Escape there, and uses the input's submit event for Return. Native XCTest exercises modifier shortcuts across one workspace session; `assert-ios-keyboard-navigation.mjs` uses AXe HID events and actual accessibility results for arrows/Return/Escape, which XCTest on iOS 18.3 cannot reliably synthesize. Neither harness treats bridge-event logs alone as a successful interaction.\n\nThe mobile launcher name is `Tolaria`; the existing package and bundle identifiers\nremain unchanged so native updates retain their application data.\nThe Expo launcher icon uses opaque 1024px light/dark variants in\n`apps/mobile/assets/icon-light.png` and `icon-dark.png`. They preserve the desktop\ndroplet paths and gradients from `src-tauri/icons/icon-source-dark.svg`, replacing\nonly its macOS rounded tile, shadow and transparent outer margin with full-bleed\nlight/dark backgrounds. iOS owns the launcher mask, avoiding an inset white tile.\nExpo supplies the native sizes and appearance catalog. Desktop assets remain\nunchanged. Rebuild the native app after changing these assets or configuration.\n\nThe Expo dynamic configuration keeps development on `com.tolaria.mob"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md — Tolaria App\n\n## 1. Development Process\n\n### Mandatory-rule exception protocol\n\nUse an exception only when a required service or analyzer remains unavailable after one retry, or when satisfying the rule is technically impossible without increasing security or data-loss risk. The repository owner is the sole approver. Before proceeding, record the blocked command, evidence, affected files, risk, compensating check, approver, and an expiry of at most 72 hours in the Linear issue or final handoff; review and remove the exception within seven days. A preference, deadline, or failing quality gate is not sufficient exception criteria.\n\n### Task tracking (Linear)\n\n**Linear is the only live queue (cutover 2026-10-15).** Desktop work lives in team Tolaria (`TOL`), project **Tolaria Desktop**; mobile work in **Tolaria Mobile**; web work in project Tolaria Web. Do not create or move Todoist tasks. The old Todoist Tolaria project is legacy (its Draft section is being triaged separately); a completed Todoist card commented \"Moved to Linear: TOL-n\" points to its Linear issue.\n\n**Linear** — workspace Refactoring, team Tolaria, issue key `TOL`.\n\n- Workflow: **Backlog** (not ready) → **Open** (ready for an agent) → **To Rework** (review failed: read the latest review comment and fix only that; pick these **before** Open) → **In Progress** (claimed) → **In Review** (pushed, awaiting Luca's QA) → **Done** / **Canceled** / **Duplicate**.\n- Claim an issue by moving it to In Progress **before** starting work. Never take another worker's In Progress issue; resume your own only when a prior claim comment identifies this worker and worktree. Re-read the issue after claiming and stop on a conflicting claim. Exception: follow the mandatory-rule exception protocol if Linear remains unavailable after one retry.\n- Labels: type `Bug` / `Feature` / `Improvement`; `mac-only` (leave for the Mac mini); `linux-ok`; `agent-blocked` (always with a comment saying why it is stuck and what would unblock it); `dubious` and `contributor-pr` (Backlog, awaiting Luca); Source group `Sentry` / `Canny` / `GitHub`. Exception: an unavailable Linear labeling service requires the same owner-approved exception protocol.\n- Reference the issue ID in commit messages, e.g. `fix: keep image width after restart (TOL-123)`.\n\n### Start working on a task\n\n**Before writing a single line of code:** inspect the configured CodeScene project's current Hotspot and Average Code Health and compare them with `.codescene-thresholds`. Then capture the file-level Code Health score for every existing code file you intend to edit. If the project is already below the threshold, **stop and refactor first** — find the worst files with the MCP, improve them, commit, then start the task. If the gate cannot be restored, stop and obtain explicit repository-owner direction before starting feature work.\n\n- Read task description and all comments fully\n- For To Rework: the ❌ QA failed comment tells you exactly what to fix\n- Check `docs/adr/` for relevant architecture decisions before structural choices\n- Check `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md` for relevant structural information\n- For UI tasks: study app visual language and components first. Prioritize reusing existing components, assets, and variables over recreating them.\n- If working on a Linear issue, add a comment: `🚀 Starting work on this task. [Brief description of approach]`\n\n### Commits & pushes\n\n- Local work may happen on `main`, in detached HEAD worktrees, or in other temporary local states. The production path is still direct-to-main: final verified work is pushed to `origin/main`, with no PR branch flow.\n- Mobile foundation is integrated into main. New mobile work follows the same direct-to-main path; the historical foundation-branch fast lane is not the current delivery workflow.\n- Commit every 20–30 min: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`\n- Pre-commit is a lightweight lint gate only. Pre-push runs the full check suite (build + tests + coverage + core Playwright smoke + CodeScene), preferably on three Chunk sidecar lanes for automatic test/coverage work: frontend lint/build/coverage, Rust coverage, and Playwright smoke. The goal is lower wall-clock time than local hooks while keeping each heavy gate isolated; keep local Playwright mainly for authoring, focused reproduction, or sidecar outages.\n- CircleCI is the authoritative outer-loop CI/CD system. `.circleci/config.yml` owns validation, native cross-platform release builds, GitHub Release publication, and Pages publication.\n- **A task is NOT done until `git push origin main` succeeds.** If the hook blocks: read the error, fix it (clippy, tests, CodeScene, build), commit the fix, push again. If a verified push is impossible, stop and obtain explicit repository-owner direction; do not use `--no-verify` without that recorded approval.\n\n### Mobile coding and release\n\nThe general coding, quality, localization, analytics, credentials and completion rules apply unchanged. Read the mobile sections of `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md`; ADRs 0247, 0263 and 0272 define OTA delivery and channel selection.\n\n- Reuse `apps/mobile/src/components/ui` native primitives and `apps/mobile/src/ui` wrappers, not desktop DOM controls. Keep phone/tablet behavior on the shared controller/repository boundary. Run `pnpm mobile:lint`, `pnpm mobile:typecheck`, `pnpm mobile:test` and focused mobile regressions as well as the normal main push gates; scoped mobile checks do not replace them.\n- Verify affected native behavior separately on iPhone, iPad and Android. Browser checks are not native acceptance. Record task-specific owner delegation of manual QA honestly; it does not waive automated gates. A locked Mac leaves affected checks pending while independent work continues.\n- QA is confined to owned disposable vaults/simulators; existing user apps, containers and vault data stay untouched.\n- CircleCI publishes mobile-related main pushes to **Alpha** after its required gates; desktop-only changes are excluded from mobile publication. Confirm the actual publication receipt, source SHA, runtime and update ID before claiming delivery. Native/configuration/dependency changes that alter the runtime require a compatible signed binary, not an incompatible OTA.\n- Use one external **Tolaria Beta** TestFlight group. Settings defaults to **Stable**, with Alpha opt-in; selected channel and running update identity are distinct. Legacy Alpha-native binaries can launch embedded Alpha before preferences load. Retain runtime matching, safe editor flush/restart and Sentry artifact verification.\n- **Stable promotion remains manual**, authorized explicitly by the owner and using the existing exact-tested-Alpha-artifact republish/approval workflow. Never rebuild from moving main or redirect Stable to moving Alpha. Do not submit Apple compliance declarations or grant new credential permissions without explicit owner authorization.\n\n### Hourly mobile queue routine\n\nKeep the scheduled prompt limited to queue orchestration; this file owns coding and release policy.\n\n1. Reuse the routine's attached worktree and inspect current work. Resume its own identified unfinished claim before taking another issue; keep execution serial under the claim protocol above.\n2. Otherwise paginate **Tolaria Mobile** To Rework, then Open; order by priority (unspecified last), then oldest creation time. Skip others' assigned/delegated work, `agent-blocked` issues and unmet dependencies. Do not pull Backlog or In Review into implementation. Follow the claim protocol above.\n3. Implement the selected issue under this file's rules. On a blocker, record the evidence and required owner action in Linear, preserve a safe checkpoint, and continue independent eligible work without mixing partial changes or extending exceptions.\n4. Put verified implementation In Review with its release record, not self-approved Done. Report meaningful milestones or actionable blockers; stay quiet when the queue or status is unchanged. Resume remaining work on the next hourly run. Keep only one active mobile queue routine.\n\n### TDD (mandatory)\n\nRed → Green → Refactor → Commit. One cycle per commit. For bugs: write failing regression test first, then fix. Exception: pure CSS/layout changes.\n\n**Test quality (Kent Beck's Desiderata):** Isolated · Deterministic · Fast · Behavioral · Structure-insensitive · Specific · Predictive. Fix flaky tests first. Prefer E2E over unit tests for user flows.\n\n### Localization (mandatory for UI copy)\n\nAll user-facing UI labels/copy must live in `src/lib/locales/en.json` and be translated into every target listed in `lara.yaml`. Exception: when the localization service remains unavailable after one retry, use the mandatory-rule exception protocol above and do not release untranslated copy without repository-owner approval. When adding or changing interface copy:\n\n```bash\npnpm l10n:translate\n```\n\nUse `pnpm l10n:translate:force` only when intentionally regenerating existing translations. Commit `src/lib/locales/*.json`, `lara.yaml`/`lara.lock` changes if produced, and verify placeholders/product names stayed intact.\n\n### Product analytics (mandatory for meaningful features)\n\nNew features should emit a PostHog event so we can see whether users actually discover and use them. Skip instrumentation when the change has no meaningful user action or when a dedicated event would create noise; record that reason in the completion comment. If the correct instrumentation remains unclear, obtain repository-owner direction before release. Use clear, stable event names, avoid PII or note content, and include only safe metadata that helps evaluate adoption and failures.\n\nWhen adding or changing a meaningful user-facing feature, include the event name(s) in the Linear completion comment alongside QA, docs, and code health. If intentionally not instrumenting a feature, explain why in the completion comment.\n\n### Code health (mandatory)\n\nPre-push compares **Hotspot Code Health** and **Average Code Health** with the exact numeric floors in `.codescene-thresholds` and fails when either score is lower. Pre-commit is lint-only; CodeScene remains mandatory through the file-level review rules below and the pre-push ratchet gate. Thresholds are a **ratchet** — only go up. When pre-push sees improved remote scores, it updates `.codescene-thresholds`, stages it, and stops so you can commit the new floor with normal verified hooks before pushing again. Exception: adding `// eslint-disable`, `#[allow(...)]`, `as any`, or another bypass requires the mandatory-rule exception protocol and repository-owner approval before the edit.\n\n**Release rule:** CodeScene is a before/after gate, not just a final score. Record the starting CodeScene state before edits and the final state after edits. If touched code gets worse, refactor before committing; if the comparison cannot be produced, stop and obtain explicit repository-owner direction.\n\nDo not edit `.codescene-thresholds` to lower the values. If the gate blocks you, improve the code; any proposed exception requires explicit repository-owner approval recorded before the edit.\n\n**CodeScene access order:** use CodeScene MCP tools if available. If MCP is unavailable, use the installed `cs` CLI for file-level review/delta work, and use the CodeScene API (`CODESCENE_PAT` + `CODESCENE_PROJECT_ID`) for project-wide Hotspot/Average threshold checks from `.codescene-thresholds`.\n\n**Before editing any existing code file:** capture its current file-level CodeScene score. After your edits, re-run the same file-level review and verify the score is higher. If the file already starts at `10.0`, it must remain `10.0`. Exception: if the score cannot be obtained after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before editing.\n\n**New files:** every new **scorable code file** must reach CodeScene score `10.0` before commit. If CodeScene reports `null` / \"no scorable code\" for a new file, it must still have zero CodeScene findings/warnings. If either requirement cannot be met, stop and obtain explicit repository-owner direction.\n\n**Before every commit:** run CodeScene file-level review on every touched or newly created code file and verify the rule above. Then run `mcp__codescene__pre_commit_code_health_safeguard` for the repository and do not commit unless its quality gates pass. **Boy Scout Rule:** every file you touch must leave with a higher score, unless it was already `10.0`, in which case it must stay `10.0`. If an analyzer or gate is unavailable, stop and obtain explicit repository-owner approval for a documented exception.\n\n**Before the final direct-to-main push:** run `mcp__codescene__analyze_change_set` with `base_ref=origin/main`. This is Tolaria's PR-preflight equivalent: every affected file must be improved or stable, and the overall quality gate must pass. Refactor and repeat if any file is degraded. Exception: if the analysis cannot complete after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before pushing.\n\n**If CodeScene gate blocks your push:** use `mcp__codescene__code_health_score` to find the worst file, refactor it, commit, push again. Do not wait for laputa-refactor because it is a background loop rather than a substitute for fixing your own regressions; if no in-scope refactor can restore the gate, stop and obtain explicit repository-owner direction.\n\n### Security scan with Codacy (mandatory)\n\nUse Codacy as a security and static-analysis gate before a task is considered releasable.\n\n- **Two required checks:** Codacy's local CLI/MCP analyzes the uncommitted working tree, while the Codacy dashboard also runs server-side tools after a commit is pushed. These are complementary gates. Report each count with its source; if either check is unavailable, stop and obtain explicit repository-owner approval instead of substituting one for the other.\n- **What counts as new code:** both a newly created code, test, script, or executable configuration file and any added code in an existing file. Scan the entire containing file, not only the added lines, because Codacy tools can report findings on unchanged lines or at file level after a change.\n- **Build the scan manifest:** before each Codacy check, enumerate added and modified tracked paths with `git diff --name-only --diff-filter=ACMR \"$(git merge-base HEAD origin/main)\" --` and untracked paths with `git ls-files --others --exclude-standard`. Classify every code, test, script, and executable configuration path as new or existing, record the manifest in the task evidence, and scan every path individually. Rebuild the manifest immediately before commit so no late-created file is omitted.\n- **Before editing existing code:** run a local file scan and record every finding for each code file you intend to touch. Also record that file's findings on Codacy's currently analyzed `main` commit using `codacy_list_files` to resolve the file and `codacy_get_file_issues` to enumerate all paginated results.\n- **Local file scan:** use `mcp__codacy__codacy_cli_analyze` with the repository's absolute `rootPath` and an explicit `file`, one touched/new file at a time. If the MCP is unavailable or fails, run `LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 .codacy/cli.sh analyze <path> --format sarif --output /tmp/codacy-<slug>.sarif`. Omit `--tool` so every locally configured analyzer runs. Explicitly inspect every `runs[].results[]` entry in the SARIF output.\n- **Analyzer-parity gate:** before creating or modifying code, call `codacy_list_repository_tools` and record the enabled dashboard analyzer name, UUID/generation, configuration mode, and language coverage. Compare that roster with the `runs[].tool.driver` entries produced locally for every language in the scan manifest. Tool names alone are not sufficient: deprecated ESLint/ESLint8 and current ESLint9 are different analyzers, and a local run of Lizard, Opengrep, and Trivy does not prove that dashboard ESLint will be clean. For TypeScript/JavaScript, the passing roster must include the current analyzer that honors `eslint.config.js`, plus every other enabled relevant dashboard tool. If the intended analyzer is disabled, a deprecated analyzer is enabled, or any enabled dashboard analyzer has no equivalent successful local run, analyzer parity fails: stop, correct the Codacy tool configuration, trigger a full reanalysis, and verify parity before code work. A repository-owner-approved exception is required if configuration access is unavailable.\n- **Approved analyzer-parity exception (Biome):** Biome is enabled on the Codacy dashboard but missing from the local Codacy CLI. Biome: covered locally by the repo's own Biome (`npx biome check <changed files>`, or the repo's existing Biome run in `pnpm codacy:gate`); approved by Luca 2026-10-10. This is a standing exception recorded here, so it needs no per-task re-approval: record that `npx biome check` run as the local Biome row in the parity and per-file evidence. It covers only the missing local Biome run; any Biome diagnostic on changed files still fails the gate, and every other analyzer must still match.\n- **Per-file analyzer evidence:** for every new code file, record one result row per expected analyzer with the exact file path, analyzer identity, completion status, and finding counts split into Critical/High/Medium/Minor/Info/unclassified. Missing rows, a skipped/untracked target, an analyzer crash, or an analyzer that silently omits the language is a failed scan, never a zero. If the CLI requires Git discovery, add the file to the index normally before the final scan; do not rely on a directory scan to discover an untracked file.\n- **Fail closed on partial scans:** a zero process exit code is not enough. Confirm the expected relevant tools produced SARIF runs and inspect scan logs for crashes, encoding failures, skipped target files, partial analysis, or missing tool output. If any relevant analyzer did not complete, the scan did not pass.\n- **Approved analyzer-coverage exception (unsupported or excluded files):** if Codacy or CodeScene does not support a file type, or excludes the path from analysis (for example test paths, `.husky/*` hook scripts), an agent does not need a pre-edit baseline or a post-edit score from that tool to edit the file. Record \"not analyzed by <tool>: <unsupported|excluded>\" as that tool's row in the parity/per-file evidence. Likewise, when an enabled dashboard analyzer has no local equivalent for a file type (for example Jackson Linter for `src/lib/locales/*.json`), cover it locally with the repo's own check (`node scripts/validate-locales.mjs` plus a JSON parse for locale catalogs) and record that run. Approved by Luca 2026-10-13; standing, no per-task re-approval. Every other gate still applies: the other tool's baseline and Boy Scout rule where it does analyze the file, zero new findings, tests, coverage, pre-push hooks and post-push verification.\n- **Zero-new-findings rule:** added or modified code must introduce no Codacy finding at any severity, including Info/Minor, Warning/Medium, High/Error/Critical, unclassified findings, and findings reported on unchanged lines or at file level because of the change. If this cannot be achieved, stop and obtain explicit repository-owner approval for a documented exception.\n- **New code — absolute zero rule:** every new code file and every file containing newly added code must be scanned explicitly after creation or modification and must have exactly zero findings attributable to the new code at every severity. A newly created file must have exactly zero findings in the whole file. For added code in an existing file, any finding on an added line, caused by an added symbol or dependency, or newly appearing at file level fails the gate. `0 Critical + 0 High + 0 Medium + 0 Minor + 0 Info + 0 unclassified` is the only passing result. If a scanner cannot prove this, stop and obtain explicit repository-owner approval before proceeding.\n- **Boy Scout Rule for existing files:** after editing, every touched file must have fewer local findings than its recorded baseline; a zero-finding file must stay at zero. Fix every existing Critical/High finding in a touched file; if this cannot be achieved, stop and obtain explicit repository-owner approval for the documented exception.\n- **Before every commit:** re-scan every touched/new code file individually and compare the complete SARIF results with its recorded baseline. Do not rely on added-line filtering, repository totals, or `pnpm codacy:gate` alone; if an individual scan cannot complete, stop and obtain explicit repository-owner approval.\n- **Post-push dashboard verification:** after `git push origin main`, wait until `codacy_get_repository_with_analysis` reports the exact pushed SHA as `lastAnalysedCommit`. Re-read the enabled-tool roster, paginate `codacy_list_repository_issues` to completion, and filter by each exact manifest path; use file-level MCP queries as supporting detail, not as a substitute for the complete repository issue list because newly created files may not yet resolve through file lookup. Absence from the issue list counts as zero only when the exact SHA is analyzed, the analyzer roster still matches the pre-edit parity record, and the repository reports no analyzer problem. Every new code file must have zero dashboard findings at every severity from every enabled analyzer; every existing touched file must have no new finding and must have fewer findings than its pre-edit dashboard baseline (or remain at zero). If the dashboard reports a finding—even one from a deprecated or misconfigured analyzer—the task remains unfinished until the file is fixed or the analyzer configuration is corrected and a replacement exact SHA is fully reanalyzed. Exception: if verification remains unavailable after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- **Repository-wide dashboard counts:** use `codacy_get_repository_with_analysis` for the total and `codacy_list_repository_issues` with full pagination for severity/tool breakdowns. Derive the provider/organization/repository from the Git remote without printing credential-bearing remote URLs; for this repository use `gh` / `refactoringhq` / `tolaria` and branch `main`.\n- **Escalation:** if a scanner is unavailable or a finding is demonstrably false, stop and obtain explicit repository-owner approval recorded in the completion comment. Rule suppression requires that same explicit approval and documentation.\n- `pnpm codacy:gate` is a required fail-closed added-line safety net in pre-push and CI; it does not replace the touched-file before/after check.\n\n### Check suite (runs on every push)\n```bash\npnpm lint && npx tsc --noEmit && pnpm test && pnpm test:coverage  # frontend ≥71%\ncargo test && cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85\n```\n\nCoverage is a release gate, not a vanity metric:\n- Frontend coverage must stay ≥71%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- Rust line coverage must stay ≥85%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- For bug fixes, add a regression test when practical.\n- For new behavior, add targeted coverage close to the changed code; do not rely only on broad E2E coverage.\n\n### UI and native QA\n\n**Phase 1 — Playwright (only for core user flows):**\n\nWrite Playwright test in `tests/smoke/<slug>.spec.ts` when the feature touches vault open, note create/save/delete, search, wikilink navigation, git commit/push, or conflict resolution. Tag a test with `@smoke` when it protects one of those core pre-push workflows; keep cosmetic or mock-heavy checks in the full regression lane. Prefer `.chunk/run-playwright-smoke.sh` on a Chunk sidecar for the curated smoke lane because local Playwright is expensive; keep `pnpm playwright:smoke` available for focused local reproduction. The curated smoke suite must stay under **5 minutes** when sharded on sidecars. Exception: if a platform outage prevents the smoke test from running after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n\n```bash\npnpm dev --port 5201 &\nsleep 3\nBASE_URL=\"http://localhost:5201\" npx playwright test tests/smoke/<slug>.spec.ts\n```\n\n**Phase 2 — Native app QA (macOS):**\n\n> On a Linux agent box, skip this Phase 2 block and follow **Linux agent boxes (Hetzner / remote)** below instead.\n\n```bash\npnpm tauri dev &\nsleep 10\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh laputa\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/qa-native.png\n```\n\nUse computer-use/browser-control interaction for native UI QA when either tool is present in the current environment: click, hover, drag, select, scroll, and type the way a real user would with the mouse and trackpad. For every UI feature, test the primary mouse-driven path first, then verify each keyboard shortcut or keyboard-first workflow implemented or modified by the task. If neither interaction tool is present, run the scripted focus, screenshot, and shortcut checks and document that limitation.\n\nUse `osascript` for app focus, keyboard shortcuts, and keyboard-specific checks. **⚠️ WKWebView:** if an `osascript keystroke` fails to enter editor text, use computer use for the native editor interaction and rely on Playwright for deterministic text-input coverage. Write the result as a Linear comment (✅ or ❌), or in the final handoff when no Linear issue exists.\n\n### Linux agent boxes (Hetzner / remote)\n\nThese rules apply when the agent runs on a Linux box (no macOS desktop, no `osascript`, no `~/.openclaw`). The macOS instructions above stay in force for the Mac mini; do not remove or weaken them.\n\n**Mac-native tasks stay with the Mac mini.** Do not claim tasks labeled `mac-only`, or tasks that clearly need macOS/Windows-native surfaces: updater / auto-update, Homebrew, macOS menus and Cmd/Option shortcut wiring, native file/open/save dialogs, signing/notarization, Windows installers. Leave them in the queue for the Mac mini.\n\n**QA on Linux = the Linux Tolaria build with a throwaway vault.** There is no macOS native QA on these boxes. Build the Linux app (`CI=true pnpm tauri build --debug --no-bundle`) and drive it under Xvfb with a throwaway copy of `demo-vault-v2/` — on the agent boxes use the installed `tolaria-qa` CLI (`tolaria-qa help`: `start`, `click`, `key ctrl+…`, `xtype`, `shot`, `smoke`, `stop`). Never open, create, or modify `~/Laputa` or any real user vault. Test the primary mouse path first, then each keyboard shortcut you added or changed (`Ctrl` on Linux where macOS uses `Cmd`). Phase 1 Playwright still applies. Record QA (✅/❌, what was clicked/typed, screenshots) in the task comment as usual.\n\n**Before pushing to main:** the full check suite passes; then `git fetch origin && git rebase origin/main` (stop on conflicts you cannot resolve safely), re-run the checks (including the Linux build and a Linux app smoke) on the rebased HEAD, then `git push origin main` so the pre-push hook runs against the latest `origin/main`. Never `--no-verify` without a recorded repository-owner exception.\n\n### Release-readiness checklist\n\nBefore pushing or moving a task to In Review, verify the release gates and add a **completion comment** to the Linear issue. Exception: when the work has no Linear issue, put the identical evidence in the final handoff and identify it as the release record. The record must include:\n\n- What was implemented (a few lines covering logic and UX/UI).\n- QA: what was tested and how (Playwright / native screenshot / osascript on macOS; Linux app driven with a throwaway vault on Linux agent boxes).\n- Tests/coverage: commands run and final coverage result.\n- CodeScene: before/after touched-file checks, the pre-commit safeguard verdict, the final `origin/main` change-set verdict, plus final Hotspot and Average scores after push; every gate must pass `.codescene-thresholds`, or the record must include explicit repository-owner approval for a documented exception.\n- Coverage commands passed (`pnpm test:coverage` and `cargo llvm-cov ... --fail-under-lines 85`) or the change is docs-only.\n- Codacy: the final scan manifest; local before/after findings for every touched file; confirmation that every relevant local analyzer completed; and post-push dashboard findings after Codacy analyzed the exact pushed SHA. Confirm fewer findings in existing files (or zero stayed zero), exactly zero local and dashboard findings in every new code file, and zero findings attributable to added code in existing files at every severity.\n- Localization: any user-facing copy lives in `src/lib/locales/en.json`, `pnpm l10n:translate` was run, and `pnpm l10n:validate` passes. If no copy changed, say “Localization: no UI copy changes”.\n- PostHog: meaningful new user actions/events are instrumented with safe metadata; noisy/minor changes explicitly say “PostHog: no event needed because …”.\n- Refactoring: any files refactored to meet the CodeScene gate, or \"none needed\".\n- ADRs: any new/updated ADRs, or \"none\".\n- Docs: any updated docs (`docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, etc.), or \"none\".\n- Demo vault dirt checked: `git status --short -- demo-vault demo-vault-v2` is empty unless fixture changes are intentional.\n\n### ADRs & docs\n\nADRs live in `docs/adr/`. Create one in the same commit as the code. Preserve existing ADRs by creating a new one that supersedes them; editing an existing ADR requires an explicit repository-owner request. Use `/create-adr`. **When:** new dependency, storage strategy, platform target, core abstraction, cross-cutting pattern. **Not for:** bug fixes, styling, refactors.\n\nAfter any Tauri command, new component/hook, data model change, or new integration: update `docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, and/or `docs/GETTING-STARTED.md` in the same commit.\n\n---\n\n## 2. Product Rules\n\n### Demo vault hygiene (`demo-vault/`, `demo-vault-v2/`)\n\nDefault to `demo-vault-v2/` for testing.\n\n- Treat `demo-vault/` and `demo-vault-v2/` as disposable QA fixtures unless the task explicitly changes demo content.\n- If you create untracked notes, attachments, or other temporary files there for testing, delete them before the task is complete.\n- If you modify tracked demo-vault files only to test or QA behavior, revert those edits before the final commit.\n- Before declaring a task done, make sure `git status --short -- demo-vault demo-vault-v2` is empty unless demo fixture changes are part of the task.\n- If a fresh run starts and the only local dirt is inside `demo-vault/` or `demo-vault-v2/`, clean those paths first and continue. That case is recoverable QA residue, not a blocker.\n\n### User vault (`~/Laputa/`)\n\nDefault to `demo-vault-v2/`. If you must use `~/Laputa/` for testing:\n- Do not commit or push test notes to the remote vault unless the repository owner explicitly identifies that content as an intended deliverable.\n- **Delete all test notes from disk** when done — do not leave untitled or temporary notes on the filesystem. Run `cd ~/Laputa && git checkout -- . && git clean -fd` to restore the vault to its last committed state.\n- **Rationale:** test notes pollute the local vault over time, making it a collection of nonsensical untitled files. Keep the vault clean on disk as well as on the remote; any intended fixture exception requires explicit repository-owner approval.\n\n### UI components — mandatory rules\n\nUse shadcn/ui components for user-facing interactive elements instead of raw HTML form elements (`<input>`, `<select>`, `<button>`, native `<input type=\"date\">`, etc.). If no suitable shadcn or existing app component exists, stop and obtain explicit repository-owner design approval before introducing a raw control.\n\n| Need | Use |\n|---|---|\n| Text input | `Input` from shadcn/ui |\n| Dropdown/select | `Select` from shadcn/ui |\n| Date picker | `Calendar` + `Popover` from shadcn/ui (NOT native `<input type=\"date\">`) |\n| Button | `Button` from shadcn/ui |\n| Autocomplete/combobox | Reuse existing combobox components from the app (check `src/components/`) |\n| Wikilink picker | Reuse the wikilink autocomplete component already used in the editor and Properties panel |\n| Emoji picker | Reuse the emoji picker component already used for note/type icons |\n| Color picker | Reuse the color swatch picker used for type customization |\n| Toggle/switch | `Switch` or `ToggleGroup` from shadcn/ui |\n| Dialog/modal | `Dialog` from shadcn/ui |\n\n**Component search trigger:** when the requested interaction does not map to a component in the table above, search `src/components/` by the interaction name and ARIA role before building a component. **Visual language:** new UI must follow Tolaria's existing components and design tokens; a deliberate exception requires explicit repository-owner design approval recorded through the mandatory-rule exception protocol.\n\n---\n\n## 3. Reference\n\n### Release notes\n\nRelease notes live in `release-notes/<tag>.md` and are published to GitHub Releases and the public website.\n\n**Format:**\n- Every release note must start with frontmatter; exceptions require the mandatory-rule exception protocol:\n  ```markdown\n  ---\n  type: Note\n  _organized: true\n  ---\n  ```\n- After frontmatter, write the user-facing content (features, improvements, fixes)\n- Use clear headings: `## New Features`, `## Improvements`, `## Stability and Fixes`\n- Use emojis and **bold** feature names for scanability\n- Write for end users, not developers\n\n**Frontmatter handling:**\n- The vault displays release notes as organized `Note` types\n- GitHub Release bodies and website copies automatically strip frontmatter via `scripts/strip-frontmatter.mjs`\n- Never expose frontmatter in user-facing surfaces\n\n**Creating a new release note:**\n```bash\ncat > release-notes/<tag>.md << 'EOF'\n---\ntype: Note\n_organized: true\n---\n\n## New Features\n\n- 🎉 **Feature name** — Description.\n\n## Improvements\n\n- ✨ **Enhancement** — Description.\nEOF\n```\n\nThe frontmatter ensures release notes are already organized when the Tolaria repo is opened as a vault, avoiding inbox clutter.\n\n### macOS / Tauri gotchas\n\n- `Option+N` → special chars on macOS. Use `e.code` or `Cmd+N`\n- Tauri menu accelerators: `MenuItemBuilder::new(label).accelerator(\"CmdOrCtrl+1\")`\n- `app.set_menu()` replaces the ENTIRE menu bar — include all submenus\n- `mock-tauri.ts` silently swallows Tauri calls — not a substitute for native testing\n\n### QA scripts\n\n```bash\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh Tolaria\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/out.png\nbash ~/.openclaw/skills/tolaria-qa/scripts/shortcut.sh \"command\" \"s\"\n```\n\n### Diagrams\n\nPrefer Mermaid (`flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram-v2`). ASCII only for spatial wireframe layouts.\n\n## Stuck or blocked tasks\n\nWhenever an agent sets the `agent-blocked` label, moves a task back to Open, or otherwise leaves a task stuck, it must ALWAYS add a task comment explaining why: the exact blocker, the evidence (command/log path), and what decision or action would unblock it. Never leave a task blocked without that comment. Exception: a Linear outage after one retry requires the identical evidence in the final handoff and repository-owner approval under the mandatory-rule exception protocol.\n","isInternal":false,"tokens":7829,"sizeBytes":35901},{"name":"ARCHITECTURE.md","path":"docs/ARCHITECTURE.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/docs/ARCHITECTURE.md","title":"docs - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"# Architecture\n\n## Desktop Note Save Transport\n\n`persistContent` sends native desktop saves through `save_note_content_bytes`\nfor notes over 256 Ki characters, using raw UTF-8 content and a base64 UTF-8 `x-tolaria-note-path` header. The\ncommand rejects malformed requests and delegates to `save_note_content`, retaining\nactive-vault path validation and the existing off-thread atomic writer. This\nbounds the JSON note payload and avoids a full JSON copy of embedded image payloads; memory remains proportional\nto note length. Browser fixtures retain their JSON save API. See ADR 0278.\n\nTolaria is a personal knowledge and life management desktop app. It reads a vault of markdown files with YAML frontmatter and presents them in a four-panel UI inspired by Bear Notes.\n\n## Design Principles\n\n### Filesystem as the single source of truth\n\nThe vault is a folder of plain markdown files. The app never owns the data — it only reads and writes files. The cache, React state, and any in-memory representation are always derived from the filesystem and must be reconstructible by deleting them. When in doubt, the file on disk wins.\n\n### Convention over configuration\n\nTolaria is opinionated. Standard field names (`type:`, `status:`, `url:`, `Workspace:`, `belongs_to:`, `related_to:`, `has:`, `start_date:`, `end_date:`) have well-defined meanings and trigger specific UI behavior — without any setup. Relationship defaults are stored in snake_case on disk and humanized in the UI. This is not convention *instead of* configuration: users can override defaults via config files in their vault (e.g. `config/relations.md`, `config/semantic-properties.md`). But the defaults work out of the box, and most users never need to touch them.\n\nThis principle directly serves AI-readability: the more structure comes from shared conventions rather than per-user custom configurations, the easier it is for an AI agent to understand and navigate the vault correctly — without needing bespoke instructions for every setup.\n\n### Where to store state: vault vs. app settings\n\nWhen deciding where to persist a piece of data, ask: **\"Would the user want this to follow them across all their Tolaria installations — other devices, future platforms (tablet, web)?\"**\n\n| Follows the vault | Stays with the installation |\n|-------------------|-----------------------------|\n| Type icon, type color | Editor zoom level |\n| Pinned properties per type | API keys (OpenAI, Google) |\n| Sidebar label overrides | Auto-sync interval |\n| Property display order | Window size / position |\n| Per-note `_width` rich-editor width override | Default rich-editor note width |\n| Applicable vault or parent-repository `.gitignore` patterns | Whether this installation hides Gitignored files |\n| N/A | Whether this installation shows Git features |\n| N/A | Git executable provider (`native` vs WSL2 distribution) |\n| Per-vault All Notes note-list column overrides | All Notes PDF/image/unsupported file visibility |\n| N/A | Per-vault Git setup prompt opt-out |\n| Type `_sidebar_label` overrides | Whether this installation auto-pluralizes type labels |\n| N/A | Registered workspace labels, aliases, mount state, and default new-note destination |\n| N/A | Mobile AutoGit, appearance, interface scale and analytics consent |\n| Any user-visible customization of how content is organized or displayed | Any machine-specific or credential-type setting |\n\n**Rule:** If the information is about *how the content is structured or presented* and the user would expect it to be consistent wherever they open their vault, store it in the vault (frontmatter of the relevant note, using the `_field` underscore convention for system properties). If it's about *this specific installation of the app*, store it in app config JSON under `$XDG_CONFIG_HOME/com.tolaria.app/` (defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms), or in localStorage for renderer-only transient layout state. The native resolver keeps XDG as the preferred root when the current user can write the target; if that root is not writable, it uses the platform config root instead and reads that writable fallback before stale XDG files. See [ADR-0177](./adr/0177-writable-fallback-for-app-config.md).\n\nMobile installation preferences use the app-owned document configuration\ndirectory with validated pending-file publication and backup recovery (ADR 0212).\nManaged Git vaults retain the existing save/operation queues. Manual sync requires\nan explicit local commit; opt-in AutoGit schedules foreground idle checkpoints\nand synchronization without promising background execution.\n\nMobile Git merges validate synthesized frontmatter and saved-view/configuration\nblobs before publishing the proposed tree (ADR 0220). Invalid metadata leaves\nHEAD, index and working files untouched for explicit conflict resolution.\nGit sync has an independent operation mutex and uses short editor/write barriers\naround checkpoints and incoming checkout, not HTTP waits (ADR 0221). Temporary\nfailures retry with bounded backoff, with Expo network reconnection as a retry\nhint. Authentication and conflict failures require attention.\nInstallation-local sync history is keyed by managed vault ID and published with\nbackup recovery (ADR 0222). It records successful remote/provider operations\nseparately from local commits and pending edits, without storing user content.\nThe same ledger keeps installation-local, revisioned paths for pending note dots\n(ADR 0271). A successful checkpoint acknowledges only the captured revisions;\nedits made while transport is running remain pending. These hints never replace\nfresh Git/content comparisons or authorize checkout.\nNative conflict review shares local/remote/keep-both choices across Git and linked\nfolders (ADR 0223). Git retains both parents; coordinated folder publication\nretains private original snapshots and an interruption journal. Comparisons are\nversion-checked again before applying any choices.\n\nNew iOS folder selections retain a Files-provider bookmark alongside an offline\nworking copy (ADR 0217). Native, coordinated three-way reconciliation writes\nlocal edits back and imports external changes; divergent versions stop for\nresolution. This is independent of Git. Provider delivery, including iCloud's\ncross-device upload, remains the provider's responsibility. Existing copy-only\nvaults are never silently attached to an original folder.\n\nExamples:\n- ✅ Vault: `_pinned_properties` in a Type note (every device should show the same pinned properties)\n- ✅ Vault: `_icon: shapes` in a Type note (icon is part of the type's identity)\n- ✅ Vault: `_width: wide` in a note that already has frontmatter (per-note reading/editing preference)\n- ✅ App settings: `zoom: 1.3` (machine-specific preference)\n- ✅ App settings: `ui_language: \"zh-CN\"` (installation-specific UI language)\n- ✅ App settings: `note_width_mode: \"wide\"` (installation-specific default for notes without an override)\n- ✅ App settings: `date_display_format: \"friendly\"` (installation-specific date rendering preference)\n- ✅ App settings: `sidebar_type_pluralization_enabled: false` (installation-specific sidebar label preference)\n- ✅ App settings: `all_notes_show_images: true` (installation-specific All Notes file-category visibility)\n- ✅ App settings: `git_provider: \"wsl\"` plus `git_wsl_distro: \"Ubuntu\"` (machine-specific Git executable location)\n\n### No hardcoded exceptions\n\nNo field names, folder paths, or vault-specific values should be hardcoded in the application source code. What can be a convention should be a convention. What needs to be configurable should live in a file. Relationship fields are detected dynamically by checking whether values contain `[[wikilinks]]` — no hardcoded field name lists.\n\n### AI-first knowledge graph\n\nNotes are not just documents — they are nodes in a structured graph of people, projects, events, responsibilities, and ideas. Every design decision should ask: \"Does this make the knowledge graph easier for a human *and* an AI to navigate?\" Conventions that are legible to both are better than conventions that are legible only to one.\n\n### Three representations, one authority\n\nVault data exists in three forms simultaneously:\n1. **Filesystem** — the `.md` files on disk. This is the single source of truth.\n2. **Cache** — `~/.laputa/cache/<hash>.json`, an index for fast startup. Always reconstructible from the filesystem.\n3. **React state** — the in-memory `VaultEntry[]` during a session. Always derived from the cache or filesystem.\n\nThese must never diverge permanently. If they do, the filesystem wins and the cache/state are rebuilt.\n\n```mermaid\nflowchart LR\n    FS[\"🗂️ Filesystem\\n.md files on disk\\n(source of truth)\"]\n    Cache[\"⚡ Cache\\n~/.laputa/cache/\\n(fast startup index)\"]\n    RS[\"⚛️ React State\\nVaultEntry[]\\n(in-memory session)\"]\n\n    FS -->|\"scan_vault_cached()\"| Cache\n    Cache -->|\"useVaultLoader on load\"| RS\n    FS -->|\"reload_vault (full rescan)\"| RS\n    RS -.->|\"write via Tauri IPC first\"| FS\n\n    style FS fill:#d4edda,stroke:#28a745,color:#000\n    style Cache fill:#fff3cd,stroke:#ffc107,color:#000\n    style RS fill:#cce5ff,stroke:#004085,color:#000\n```\n\n#### Ownership rules\n\n| Layer | Owner | Writes to | Reads from |\n|-------|-------|-----------|------------|\n| Filesystem | Tauri Rust commands (`save_note_content`, `update_frontmatter`, etc.) | Disk | — |\n| Cache | `scan_vault_cached()` in `vault/cache.rs` | `~/.laputa/cache/` | Filesystem + git diff |\n| React state | `useVaultLoader` + `useEntryActions` + `useNoteActions` | In-memory `entries` | Cache (on load), filesystem (on reload) |\n\n#### Invariants\n\n1. **Disk-first writes**: All functions that change vault data must write to disk (via Tauri IPC) *before* updating React state. This ensures that if the disk write fails, React state remains consistent with what's actually on disk.\n2. **Optimistic UI with rollback**: Where responsiveness matters (e.g. `persistOptimistic` in `useNoteCreation`), state may update before disk confirmation — but a failure callback must revert the optimistic state.\n3. **No orphan state updates**: Never call `updateEntry()` before the corresponding `handleUpdateFrontmatter()` or `handleDeleteProperty()` has resolved. Type metadata actions in `useEntryActions` follow this rule — create the missing type document if needed, write frontmatter first, then update state. If missing type creation collides with an existing note filename, the action stops after the existing creation toast instead of applying orphaned state.\n4. **Recovery via reload**: If state ever diverges from disk (crash, external edit, race condition), `Reload Vault` (Cmd+K → \"Reload Vault\") does a full filesystem rescan via the `reload_vault` Tauri command, atomically replacing the cache and all React state. The `reload_vault_entry` command can re-read a single file.\n5. **Cache is disposable and crash-safe**: A full reload keeps the last valid snapshot readable while it builds a replacement, then swaps the new cache into place only after the scan completes. A crash during refresh therefore cannot turn the next startup into a cache-miss scan. The cache remains derived from the filesystem and can always be deleted and rebuilt.\n6. **Visibility filters are command-boundary concerns**: Gitignored-content visibility is applied after scanning/caching, before entries, folders, or search results reach React. The cache remains complete so toggling the setting can show ignored content again without rebuilding a different cache shape. Large folder filtering runs on the blocking Tokio pool and drains `git check-ignore` output while feeding stdin so broad ignore matches cannot freeze the native UI thread.\n\n#### External Change Detection\n\nEvery full App window registers its active vault roots through `start_vault_watcher` / `stop_vault_watcher` (`src-tauri/src/vault_watcher.rs`, backed by Rust `notify`). The backend reuses one native watcher per shared root, tracks owning window labels, and removes a watcher only after its final owner unregisters. Watchers emit `vault-changed` events for content paths and ignore churn from `.git/`, `node_modules/`, temp files, and `.tolaria-rename-txn`. `useVaultWatcher` batches those events and suppresses recent app-owned saves. The main window sends remaining paths through `refreshPulledVaultState()` so folders, saved views, note-list state, and clean active-editor content refresh under the ADR-0135 unsaved-edit rules. Secondary note windows reload known changed entries individually, preserving the full graph while avoiding full scans; deleted, moved, unknown, or otherwise non-reloadable paths fall back to the shared full reconciler. A same-path replacement keeps the active tab mounted, and a frontmatter-only replacement keeps the current BlockNote document, caret, focus, and scroll position. Content changes still replace the clean editor document, while a true move or deletion clears the obsolete tab; when a document replacement starts from a focused editor, the app dispatches a post-replacement editor focus request. `useVaultLoader.isReloading` drives the status-bar reload spinner for both manual and full watcher-triggered reloads.\n\nWindow focus also requests Git-based external rename detection for the active vault. The shared `commands::blocking` boundary schedules synchronous command work on Tokio's blocking pool and converts join failures to string errors, retaining contextual labels where needed. The `detect_renames` Tauri command uses this boundary for its filesystem and Git subprocess work so a slow repository cannot stall the native event loop while the user resumes typing.\n\n#### Progressive Vault Loading\n\nVault opening waits for the persisted vault registry before selecting a path, so it never scans the legacy default vault on the way to the active workspace. On a warm start, `read_vault_snapshot` deserializes a version- and path-valid active-vault cache without Git, per-entry filesystem checks, sorting, or cache replacement; command-boundary visibility filtering can remove Gitignored entries without discarding that snapshot. React installs the provisional graph and clears `isLoading`, then `list_vault` reconciles Git changes and missing files in the background. Invalid or missing snapshots and forced reloads use the authoritative scan path. Clean same-commit reconciliation avoids both the full Git-date scan and an identical cache rewrite. Folders and saved views load independently; secondary workspaces start only after the active graph is usable and hydrate one at a time. Nested mounts derive their entries from a loaded ancestor instead of scanning the same subtree again. BlockNote and editor-only styles remain outside the initial shell chunk and prewarm after its first commit; selecting a note requests them immediately.\n\nStartup performance is measured end to end with ordered, path-free milestones spanning renderer initialization, persisted settings, vault selection, snapshot hydration, interactivity, editor loading, and background reconciliation. Each milestone records both renderer-relative and native-process elapsed time; `TOLARIA_STARTUP_TRACE=1` writes the same JSON events to stderr for repeatable native measurements without adding UI. Git repository probing runs on the blocking Tokio pool, and missing Git-related environment variables are resolved with one login-shell invocation rather than one process per variable. See [ADR-0166](./adr/0166-snapshot-first-progressive-vault-startup.md) and [ADR-0170](./adr/0170-measurable-crash-safe-startup.md).\n\nLarge-vault reproduction and keyboard QA steps live in [LARGE-VAULT-LOADING-QA.md](./LARGE-VAULT-LOADING-QA.md).\n\n#### Mounted Workspaces\n\nThe registered vault list can act as a mounted-workspace set. `useVaultSwitcher` persists each workspace's installation-local identity (`label`, stable `alias`, color, mount flag) and the default destination for newly created notes in `vaults.json` under Tolaria's app config directory (`$XDG_CONFIG_HOME/com.tolaria.app/`, defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms). If the native app cannot write that XDG target, it falls back to the platform config directory for both the write and the first subsequent read. `useVaultLoader` scans every available mounted workspace and annotates each `VaultEntry` with provenance before React consumes the combined graph. The default workspace is the write target for new notes and Type documents; it is not the only active vault when multiple workspaces are enabled.\n\nExternal MCP clients share that registry boundary. `attach_vault` registers an existing canonical directory without initializing Git, while `clone_vault` uses non-interactive system Git in a temporary sibling and publishes the destination only after clone success. Both preserve the current active vault, add the new root to the calling MCP process immediately, and broadcast a registry-change intent so running renderers reload mounted-workspace metadata without relaunching or switching focus. See [ADR-0175](./adr/0175-mcp-vault-lifecycle-uses-app-registry.md).\n\nThe status-bar vault menu can open a registered vault as an independent desktop application instance. `VaultMenu` owns rendering, while `vaultMenuInteractions`, `vaultMenuActions`, and `vaultMenuTrigger` isolate mounted-workspace behavior, action construction, and trigger styling. Ordinary launches still register the single-instance plugin, while an explicit vault-instance launch bypasses it and scopes that process's active vault without changing the shared registry's ordinary startup selection. Packaged macOS builds use Launch Services `open -n` so each vault receives its own Dock tile and Command-Tab entry. `src/shared/workspaceColorContract.json` is the cross-runtime source for renderer picker keys, launch validation, persisted vault-color normalization, and light/dark native icon tints. Each process's AppKit icon is generated from the bundled Tolaria icon by recoloring its droplet with the launched or active vault's declared native tint; renderer-only `pink` and `gray` colors explicitly fall back to the contract's default blue tint. See [ADR-0171](./adr/0171-separate-vault-application-instances.md).\n\nVault item deep links use the registered vault list as their resolver namespace. `src/utils/deepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from workspace aliases, labels, and paths, appending a short stable hash when generated slugs would collide. `useDeepLinks` validates incoming links, broadcasts cross-window route requests so an already-open matching vault instance can claim and focus the link, then falls back to switching the receiving process's vault when no matching window answers. The owning window reloads the vault index once for recently changed files and opens the matching `VaultEntry` through the normal note-selection path.\n\nSaved Views participate in that mounted graph as source-scoped chrome. `useVaultLoader` loads view definitions from every mounted vault, annotates each `ViewFile` with its owning `rootPath` and workspace identity, and keeps sidebar selection/persistence keyed by `(rootPath, filename)` so same-named view files from different vaults stay independent.\n\nGit surfaces resolve repository paths explicitly. `useGitRepositories` derives the active repository set from the mounted available workspaces, keeps separate selected repositories for Changes, Pulse/history, and manual commits, and exposes the combined modified-file count for status/commands. Native Git capability checks treat a mounted workspace as Git-backed when it is inside any Git work tree, so included vault folders can reuse a parent repository without creating embedded `.git` directories. Rust resolves each vault to a `GitWorkspace` (`vault_root`, provider-discovered `git_root`, and repository-relative `vault_pathspec`). Vault-scoped status, diff, history, dates, pulse, conflict, and commit commands execute at the Git root with that pathspec, then map Git output back to vault-relative note paths. Branches, remotes, fetch, pull, push, merge, and rebase remain repository-wide; the settings row exposes the resolved root and parent-repository sync feedback identifies that wider scope. App-created commits use path-limited staging plus `git commit --only` so sibling staged and unstaged work is preserved. Mounted folders outside Git return no changes/no remote for aggregate probes instead of failing workspace-wide sync. AutoGit checkpoints iterate that repository set, while manual commit, history, diff, and discard operations use the selected surface or the note's workspace provenance. See [ADR-0161](./adr/0161-parent-git-workspace-context.md).\n\nGit command launch is also installation-local. The Rust Git module resolves a `GitLaunchConfig` from app settings before spawning Git, defaulting to native `git` while allowing Windows installations to explicitly select WSL2 Git and a distribution. WSL-selected launches go through `wsl.exe --exec git` and translate Windows/WSL UNC vault paths before passing repository paths across the command boundary; Tolaria never silently switches providers based on detection alone.\n\nRenderer git file workflows stay behind `useGitFileWorkflows`. The hook resolves per-note repository paths, queues editor diff requests, opens Pulse history entries including deleted-file previews, and keeps discard/reload handling close to the selected Git surface while `App.tsx` only wires the resulting callbacks into `NoteList`, `PulseView`, and `Editor`. `useInitialPulseLoad` separately owns cancellable Pulse initial, refresh-key, and retry requests; pagination remains in `PulseView`. Note history reloads only when its note, visibility, or explicit refresh generation changes; changing renderer callback identities during an editor-state update does not rerun `git log`, and the native history command executes that blocking process on Tokio's blocking pool.\n\nCross-workspace note reads and writes keep the disk-first invariant. When an absolute note path is saved or read without an explicit `vaultPath`, the Tauri boundary resolves the deepest registered vault root that contains the path and validates against that root before touching disk. This lets an editor tab opened from a mounted workspace save back to its source repository while preserving the same path-escape protections as active-vault operations.\n\n#### Note Opening Fast Path\n\nNote opening uses bounded in-memory fast paths for raw content and parsed editor blocks. `useTabManagement` owns the markdown/text prefetch cache and treats every cached value as a performance hint only: identity-matched entries (`modifiedAt` + `fileSize`) can be reused immediately, while identity-missing or identity-mismatched cached text is checked with `validate_note_content`, which compares the cached text with the current file bytes inside the validated vault boundary. If validation fails, Tolaria discards the cached entry and reads fresh disk content before swapping the editor.\n\nThe note list opportunistically preloads visible and adjacent markdown/text entries after a short delay. When a large warmed Markdown note resolves, `useEditorTabSwap` may parse it into a bounded parsed-block cache only after foreground editor work has been idle and the rich editor is mounted. On foreground open, block resolution checks the hot tab cache, exact-source parsed-block cache, blank/H1 fast path, and a direct Markdown-to-block parser for large common Markdown before falling back to BlockNote's parser. The direct parser runs in a module worker when available and falls back synchronously only for tests or runtimes without workers. It is conservative: unsupported Markdown constructs return to the BlockNote path rather than changing semantics. Large resolved block sets mount progressively: the editor is locked, the first chunk is applied immediately, remaining chunks append across animation frames, and the same generation/source-content token can abort the partial mount before it is committed. Progressive chunk boundaries expand through contiguous numbered-list items so BlockNote receives each ordered-list run in one mutation and preserves continuous numbering. Every programmatic document replacement, including progressive chunks and fallback recovery, is excluded from ProseMirror history so note navigation cannot retain previous document graphs or displace user typing from undo. Ordinary BlockNote block wrappers stay fully measurable after mount so hover side menus, slash dialogs, and document-end interactions do not inherit browser lazy-height scroll jumps. Parsed blocks are keyed by vault, path, and exact source content; every async swap carries a generation/source-content token so stale conversion results cannot overwrite newer file content or dirty editor state. The editor never renders a preview surface that later morphs into BlockNote. Development builds log `editorBlockResolve`, `editorBlockApply`, and `parsedBlockPreload` timings for large or slow note opens. See [ADR-0105](./adr/0105-editor-correctness-and-responsiveness-contract.md).\n\nRich-editor Markdown boundaries stay on the editor fast path while keeping one owner for import, installation, and export. `src/utils/richEditorMarkdown.ts` installs the direct serializer on real BlockNote editors, preprocesses Markdown before parsing, injects Tolaria schema blocks after parsing, and serializes body/document Markdown for saves, raw-mode entry, tab swaps, and position snapshots. Its lower-level direct serializer (`src/utils/blockNoteDirectMarkdown.ts`) serializes common Tolaria block shapes to Markdown with a per-editor weak cache keyed by stable block object identity and list context. Durable Markdown bridges for wikilinks, linked inline-code labels, math, highlights, files, Mermaid, tldraw, sandboxed HTML, blockquote callouts, and literal path-template backslashes stay behind `richEditorMarkdown.ts`; linked code and vulnerable literal backslashes are protected by Markdown-inert placeholders during import and restored before editing, while the direct serializer keeps safe identifier underscores, Windows-style separators, and line-ending-adjacent formatting delimiters stable. The bridge helpers preserve unchanged block objects so repeated debounced saves can reuse direct Markdown projections. Highlights use portable Bear-compatible prefixes for custom colors (`==🔴red==`, `==🔵blue==`, `==🟣purple==`, and `==🟢green==`) while the prefix-free `==text==` form remains the default yellow. Callouts keep marker metadata in block props but retain their body as editable inline content, and their mixed serializer restores portable `> [!type]` syntax. Stable active-tab rerenders trust the exact-source block cache before doing any full-document Markdown comparison, so unrelated React state changes do not serialize large notes. Unsupported block shapes fall back to BlockNote's lossy serializer, and development builds log `richEditorSerialize` timing/cache/fallback metrics plus rare `editorStabilityCheck` fallback comparisons for large or slow serializations.\n\nRich-editor typing keeps BlockNote's per-transaction work minimal. Tolaria creates BlockNote with animation bookkeeping disabled so the `previousBlockType` extension, which scans the old and new ProseMirror documents on every edit, is not installed. Collapsed-heading rendering attaches its mutation observer and `editor.onChange` subscription only while at least one heading/list section is collapsed, then detaches when the final section expands. Heading folds follow the document outline through divider blocks and stop at the next same-or-higher heading. Development builds wrap ProseMirror dispatch once per editor view and log `richEditorDispatch` timings for large or slow transactions, giving QA a direct signal for edit-time stutter instead of only note-open speed. The pinned `prosemirror-view` dependency normalizes a stale child boundary to the live parent end during DOM-selection synchronization so editor teardown or remount cannot dereference a child removed mid-scan; see [ADR-0183](./adr/0183-normalize-stale-prosemirror-selection-boundaries.md).\n\nCore perceived latency is protected by a synthetic production-browser benchmark. `pnpm perf` injects deterministic 500-entry startup/note fixtures and a 2,000-entry virtualized-list fixture, discards a warmup, then aggregates five measured runs. Startup phases publish standard `tolaria:<phase>` User Timing marks; note scenarios combine visible editor/content milestones with opt-in note/editor trace durations; list scrolling waits through the destination paint. `.editor-performance-thresholds.json` stores ratcheted median budgets and p90 diagnostics. CircleCI runs the gate in a dedicated browser job, while native `TOLARIA_STARTUP_TRACE=1` remains the richer host-level diagnostic tier. See `docs/PERFORMANCE.md` for the instrumentation audit and baseline policy.\n\n## Tech Stack\n\n| Layer | Technology | Version |\n|-------|-----------|---------|\n| Desktop shell | Tauri v2 | 2.10.0 |\n| Frontend | React + TypeScript | React 19, TS 5.9 |\n| Mobile shell | Expo React Native | Expo 54, React Native 0.81 |\n| Editor | BlockNote | 0.46.2 |\n| Editor render extensions | @tiptap/pm | ProseMirror decorations for rich-editor node presentation |\n| Code block highlighting | @blocknote/code-block | 0.46.2 |\n| Additional code grammars | @shikijs/langs | 3.23.0 |\n| Diagram rendering | Mermaid | 11.14.0 |\n| Whiteboard rendering | tldraw | 4.5.10 |\n| Raw editor | CodeMirror 6 + official language packages | Markdown, standalone/fenced HTML, YAML, JSON, Python, SQL, JS/TS |\n| Spreadsheet editor | IronCalc workbook + WASM | 0.5.x |\n| Styling | Tailwind CSS v4 + CSS variables | 4.1.18 |\n| UI primitives | Radix UI + shadcn/ui | - |\n| Mobile UI primitives | Nativewind + React Native Reusables via Tolaria-owned wrappers | Nativewind 4, RNR registry primitives |\n| Icons | Phosphor Icons | Full catalog; per-icon modules load on demand |\n| Build | Vite | 7.3.1 |\n| Backend language | Rust (edition 2021) | 1.77.2 |\n| Frontmatter parsing | gray_matter | 0.2 |\n| Filesystem watcher | notify | 6.1 |\n| AI (workspace) | CLI agent adapters (Claude Code + Codex + GitHub Copilot + OpenCode + Pi + Antigravity + Kiro + Hermes Agent) plus configured local/API model targets | - |\n| Search | Keyword (walkdir-based file scan) | - |\n| Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync |\n| MCP | @modelcontextprotocol/sdk | 1.0 |\n| Tests | Vitest (unit), Playwright (E2E/smoke), cargo test (Rust) | - |\n| Package manager | pnpm | - |\n\nThe icon registry derives its stable kebab-case catalog from the Phosphor CSR\nmodule filenames at build time. Each icon remains a separate Vite dynamic\nimport, so resolving an uncommon vault icon loads only that SVG component.\nThe type customization grid renders the catalog in progressive batches while\npreserving search across every registered icon. See [ADR-0174](./adr/0174-lazy-full-phosphor-icon-catalog.md).\n\n### Mobile App Foundation\n\nStandalone iOS editor text saves use the existing workspace module's asynchronous\natomic writer (ADR 0203). A private, versioned recovery record is published before\nreplacement; restart recovery precedes indexing, reads, and Git synchronization.\nUnexpected destination changes retain the pending draft and fail closed. This\nprotects published text saves, not unsent keystrokes. Structural entry operations\nuse the same recovery boundary: exclusive rename for moves, private quarantine\nbefore deletion cleanup, and UUID completion receipts for safe queue retries\n(ADR 0204). Text and entry retries consult completion receipts before capturing\nnew preconditions, so replay cannot overwrite later edits or delete recreated\nentries (ADR 0206). A move plus its subsequent backlink rewrites is not yet one transaction.\nExpo Go and Android still require their own equivalent persistence implementation.\n\nThe native text engine also supports a version-two journal containing an entire\ntext-only batch (ADR 0207). It preflights all prior digests and retains the plan\nwhen an external edit prevents replay. Single-save journals and receipts remain\nbackward readable. The repository routes entirely text-based multi-write plans\nthrough the native batch capability, coalescing repeated paths to their final\ncontent. Single saves, mixed structural plans, and runtimes without that\ncapability retain their existing individual-operation queue boundaries.\n\nThe same iOS module exposes an optional, single-call Git metadata reader anchored\nto the app sandbox. It returns fresh stat fields without caching file state;\nExpo continues to own byte IO. Rapid-edit staging and checkout recovery remain\nunchanged (ADR 0205).\n\nMobile saved-view loading uses `mobileViewYaml.ts` and the `yaml` parser with\nbounded input, alias expansion and filter depth (ADR 0210). Valid desktop YAML\nindentation and flow collections retain their filters; malformed definitions are\nrejected instead of becoming all-note views. Evaluation and serialization remain\nin `mobileSavedViews.ts`. Sidebar type discovery has no arbitrary count cap and\nuses the desktop pluralization helper, while explicit type labels take priority.\n\n`MobilePanelSafeArea` owns each tablet pane's top inset, background and divider.\nThere is no workspace footer: Settings lives at the sidebar bottom. The phone\nshell colors its top inset from the active panel. Panes and their dividers reach\nthe bottom edge; bottom controls and scrollable inspector content own their\nhome-indicator clearance without leaving a separate band below the workspace.\n`MobileGitHubRepositories` uses a bounded, searchable FlatList picker, and\n`MobileDeviceCode` copies authorization codes without routing secrets through the\ndiagnostic clipboard helper. Inspector relationship groups expose their own add\naction, and native option rows use explicit styles on the RNR button primitive.\n\nThe vault catalog remembers local imports alongside Git working copies (ADR\n0216). Each new local import has its own app-owned directory; the old single\nimport is registered in place once. One mutually exclusive active pointer is\nrestored before workspace loading. Forgetting changes only the catalog, never\nthe vault's files. Settings reuses the vault manager's GitHub account controller\nand hands off vault operations through the existing editor-save barrier.\n\nLazy note hydration reparses only the loaded target, resolves its relationships\nagainst the full note pool, and retains unrelated note object identities. It\ndoes not emit filesystem writes or change modified timestamps. The native\nread-only benchmark measures hydration separately from initial indexing and\ntype filtering; these are data-path timings, not screen-transition timings.\n\nStorage verification can also run against an optimized local binary using the\nexplicit `TOLARIA_QA` Swift condition (ADR 0208). A read-only native capability\nenables the existing loopback-only probes without Metro or development JavaScript.\nOrdinary Release excludes that native API. Git QA records JavaScript timer delay\nseparately from clone/push/pull duration; neither measure represents native frame\nrate or a physical-device performance budget.\n\nPhone layout uses `useMobileDeviceLayout` instead of an orientation-sensitive\nwindow-width breakpoint. Its actual panels occupy fixed slots on one retained\nrail; `usePhonePanelRail` drives that rail through RNGH and Reanimated on the UI\nthread. Incoming panes are not duplicate previews, and settling does not reset\nthe offset or start a second entrance animation. `usePhoneViewportWidth` accounts\nfor the outer safe area once, so landscape panes and edge gestures use the real\ncontent viewport. Inactive content is excluded from input and accessibility;\nthe editor is created on its first visit and retained thereafter. See ADR-0224\nand ADR-0244. Native acceptance covers cancellation, draft/focus continuity,\nscroll position, rotation, note-row actions and corresponding iPad behavior.\n\nThe mobile app lives in `apps/mobile` as a separate Expo React Native workspace package. It is intentionally not a port of the desktop React DOM component tree. Desktop components, CSS variables, and panel semantics are treated as the reference contract, while mobile uses native primitives and fixture-driven screens. Distribution builds use Expo SDK 57 with the supported `expo-build-properties` scene-lifecycle opt-in for Xcode 27/iOS 27 compatibility (ADR 0246). Prebuild generates Expo's scene manifest and window owner; the temporary Expo Go 54 preview constraint is superseded. Native dependencies and scene support require a new signed build and OTA fingerprint, not a JavaScript-only update.\n\nThe first mobile surface is a UI lab with real workspace semantics behind a fast fixture default. `apps/mobile/src/components/ui` owns RNR-derived shadcn-style primitives such as text, buttons, and badges, while `apps/mobile/src/ui` owns Tolaria-specific wrappers such as icon buttons, panels, toolbars, list rows, chips, and property rows. Workspace components live under `apps/mobile/src/components/workspace` and consume `MobileWorkspaceSnapshot` from `apps/mobile/src/workspace/mobileWorkspaceModel.ts`. Tablet panel gestures use one workspace-level horizontal responder and Reanimated offsets. The sidebar and note list remain mounted as one strip with desktop-token snap points at 0, -260, and -600 pixels, so a drag begun anywhere in workspace content can directly hide or reveal both panels without lifting. Only a hidden Properties panel reserves a 32-pixel right-edge reveal gesture; the editor toolbar exposes the equivalent explicit toggle. The native panel-gesture QA maps React Native accessibility frames onto the rotated Simulator window, injects sampled real drags from content and the screen edge, and verifies each settled panel state through stable test identifiers. The default repository remains fixture-backed for screenshot speed, while the Playwright QA harness can inject a real read-only local-vault snapshot through `tolaria:mobile-workspace-snapshot` when `MOBILE_QA_VAULT_PATH` is set. Explicit `source=native-vault` mode uses `expo-file-system` through `apps/mobile/src/workspace/expoWorkspaceFileSystem.ts`, reads an Expo-accessible vault directory, and persists reducer write plans back to markdown files and directories. On iOS, selecting a vault imports it atomically into the app-managed `Documents/Tolaria Vault` directory because document-picker access to arbitrary external folders cannot be reacquired after process termination; the custom `TolariaWorkspaceAccess` Expo module restores that managed local copy on cold launch and the app exposes Documents through Files. For launch performance, that module enumerates the managed vault off the JavaScript thread and returns one serialized file index to the existing pure snapshot builder, avoiding thousands of per-file React Native bridge calls; editor hydration and reducer write plans continue to use the filesystem repository rather than treating the launch index as a second persistence model. `apps/mobile/src/workspace/localVaultSnapshot.ts` keeps scanning as a pure files-plus-folder-paths transform: it parses Tolaria frontmatter conventions, preserves explicit empty folder paths for the sidebar, derives type colors from Type documents, caps rendered note rows for fast UI work, and is shared by host-vault QA and native-vault mode.\n\nTablet editing is reducer-owned and repository-persisted. `apps/mobile/src/workspace/mobileWorkspaceEditing.ts` applies note creation, title/body edits, frontmatter scalar property changes, relationship add/remove, archive, favorite, folder create/rename/delete, saved-view definition changes, primary note-list display-property config changes, and Type definition actions against the current `MobileWorkspaceSnapshot`. Note folder moves and explicit filename renames emit `moveNote` file-write plans plus only the `saveNote` writes needed for backlink rewrites, while folder edits update explicit folder paths, retarget notes in renamed subtrees, rewrite affected path-based wikilinks, and emit directory write plans instead of modeling folder renames as note delete/save loops. Saved-view edits persist desktop YAML fields including filters, `order`, and `listPropertiesDisplay`, so mobile view ordering and saved-view note-list column choices use the same file contract as desktop. Primary All Notes and Inbox display-property choices emit `saveVaultConfig` plans and are stored outside the vault file list by the repository adapter, matching the desktop vault-path-local config contract. Type document create/delete/update/reorder logic lives in `apps/mobile/src/workspace/mobileWorkspaceTypeEditing.ts` and persists the Type markdown document frontmatter for sidebar label, visibility, color, sort, note-list display properties, and instance schema/default fields through `apps/mobile/src/workspace/mobileTypeDefinitions.ts`; `apps/mobile/src/workspace/mobileTypeDefinitionSchema.ts` owns the form conversion between custom scalar properties, wikilink relationship fields, and blank built-in relationship placeholders. `apps/mobile/src/screens/tabletWorkspaceHistory.ts` records the mobile command history at the snapshot boundary: content/frontmatter edits replay as content writes, structural note, folder, saved-view, and Type-definition changes replay through exact restore edits, and `apps/mobile/src/screens/tabletWorkspacePathHistory.ts` routes note moves, note filename renames, and folder subtree renames back through inverse reducer commands so undo/redo preserves filenames, ordering, raw Markdown, backlink rewrites, and persisted write paths. `apps/mobile/src/workspace/mobileNeighborhood.ts` mirrors the desktop Neighborhood note-list contract for mobile snapshots by pinning the source note, grouping Type instances, direct relationships, inverse relationships, and Backlinks with the same desktop group labels, and preserving duplicate notes across groups. The reducer preserves frontmatter while the markdown editor edits body content, derives snippets/blocks/link targets back from Markdown, resolves wikilink relationship targets against the snapshot, and returns write plans. The source editor and web WYSIWYG fallback keep keystrokes in a local draft and flush through that reducer after a short idle window or when editing unmounts, matching the desktop edit-buffer boundary instead of rebuilding the workspace graph for every character. Mobile note snapshots preserve frontmatter aliases so wikilink resolution and editor autocomplete can match desktop identity candidates. The repository boundary decides whether those writes are no-ops for fixtures, host-injected QA logs, installation-local config writes, or native Expo filesystem writes. Visual components do not import filesystem APIs directly.\n\nThe native mobile WYSIWYG editor is wrapped behind `MobileWysiwygMarkdownEditor`. It registers Tolaria-owned TenTap bridge extensions only at that boundary and passes a generated Tolaria TenTap WebView source through `customSource`, so custom Tiptap nodes exist inside the editor WebView instead of only in React Native. `MobileWysiwygContentBridge.ts` hydrates programmatic document content without emitting user-edit updates, and the React Native lifecycle keeps persistence gated until an explicit touch reaches the editor surface; lazy note hydration therefore cannot be mistaken for a user edit, while the first intentional keystroke still saves normally. `MobileWysiwygMathBridge.ts` adds the `mathInline` and `mathBlock` nodes required for desktop math parity and renders both through the mobile KaTeX MathML helper. `MobileWysiwygTentapWebEditor.tsx` plus `apps/mobile/scripts/build-wysiwyg-editor-html.mjs` own the rebuildable WebView bundle. `mobileInlineMath.ts`, `mobileDisplayMath.ts`, `mobileDocumentContent.ts`, and `mobileWysiwygInputTransforms.ts` keep parsing, hydration, structural input transforms, and Markdown serialization pure and testable. Native WYSIWYG QA probes live beside the mobile editor boundary and are opened through Expo deep links; external links now have a dedicated simulator proof that applies and removes a TenTap link command, then verifies the serialized Markdown output rather than relying only on model tests.\n\nMobile slash commands and inline Find/Replace use that same TenTap boundary. Their ProseMirror plugins own query matching, decorations, keyboard selection and undoable document changes inside the WebView; narrow bridge messages expose state and actions to native RNR controls. Find uses the desktop's pure matching and safe-regex helpers without importing desktop UI. The mounted editor registers Find/Replace with the existing editor-command registry, so keyboard shortcuts and overflow actions reach the inline bar instead of a workspace modal. Source-mode Find uses the same bar and overlays its active match in the existing Markdown syntax layer, retaining YAML frontmatter. Tests mount the actual TenTap web runtime to avoid mixing incompatible ProseMirror instances, and native XCTest checks touch targets, inline placement and serialized Markdown.\n\nMobile note deep-link copy mirrors the desktop URL contract without importing desktop React DOM modules. `apps/mobile/src/workspace/mobileDeepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from the mobile snapshot source and selected note path, while `apps/mobile/src/workspace/mobileClipboard.ts` writes through Expo Clipboard. The action is exposed through the tablet controller/action-sheet callback boundary so visual rows remain platform-agnostic.\n\nMobile note PDF export follows that same boundary. `apps/mobile/src/workspace/mobilePdfExport.ts` strips YAML frontmatter through the mobile document-content helper, renders the note body to Tolaria-styled HTML, and uses `expo-print` plus `expo-sharing` only when running in the native Expo app. Browser-based UI lab checks record deterministic export attempts instead of opening a print dialog, and the generated PDF is treated as a temporary share artifact rather than a new vault file.\n\nMobile attachment import and opening also follow the controller/editor callback boundary. `apps/mobile/src/workspace/mobileAttachmentImport.native.ts` uses `expo-document-picker` plus the Expo FileSystem object API to copy selected files into the active vault's `attachments/` directory, while `mobileAttachments.ts` owns safe filenames and portable Markdown image/link serialization. `apps/mobile/src/workspace/mobileAttachmentUris.ts` is the native-boundary adapter that resolves portable `attachments/...` Markdown targets to Expo file URIs for TenTap/link opening, then converts vault-local file URIs back to portable Markdown before save. Source and WYSIWYG editor surfaces receive importer/open-link callbacks and attachment payloads instead of importing native picker, sharing, or filesystem modules directly.\n\nThe mobile command palette mirrors the desktop command boundary without importing desktop React hooks. `apps/mobile/src/workspace/mobileCommandPalette.ts` builds enabled mobile command rows from existing workspace callbacks, preserving shared IDs from `src/shared/appCommandManifest.json` when a command is manifest-backed and using desktop dynamic command-palette IDs for rows such as note retargeting, file utility actions, and note-width changes. Selection-sensitive editor actions such as paste-without-formatting are registered by the mounted source or native WYSIWYG editor through `apps/mobile/src/workspace/mobileEditorCommands.ts`, so tablet and phone palettes can dispatch the active editor command without importing editor internals. `apps/mobile/src/components/workspace/MobileCommandPalette.tsx` owns only the native searchable/grouped surface; create/search/navigation/note/file/view/settings execution remains in the reducer/controller callbacks. Hardware keyboard commands are exposed through the tracked `TolariaKeyCommands` Expo module: its React delegate handler supplies the root UIKit controller and registers priority `UIKeyCommand`s, then forwards normalized events to the same JavaScript router used by DOM keyboard QA. The root controller accepts first-responder status when no text input owns focus. JavaScript retains one subscription across renders and reads current handlers through a ref. Cmd+1/2/3 and Cmd+Shift+I use the existing tablet panel controller. Unmodified arrows belong to the focused native text input, the open command palette, or the visible note list, in that order; modal/hidden lists cannot consume them. The palette explicitly registers its lifetime with the native module, receives arrows/Escape there, and uses the input's submit event for Return. Native XCTest exercises modifier shortcuts across one workspace session; `assert-ios-keyboard-navigation.mjs` uses AXe HID events and actual accessibility results for arrows/Return/Escape, which XCTest on iOS 18.3 cannot reliably synthesize. Neither harness treats bridge-event logs alone as a successful interaction.\n\nThe mobile launcher name is `Tolaria`; the existing package and bundle identifiers\nremain unchanged so native updates retain their application data.\nThe Expo launcher icon uses opaque 1024px light/dark variants in\n`apps/mobile/assets/icon-light.png` and `icon-dark.png`. They preserve the desktop\ndroplet paths and gradients from `src-tauri/icons/icon-source-dark.svg`, replacing\nonly its macOS rounded tile, shadow and transparent outer margin with full-bleed\nlight/dark backgrounds. iOS owns the launcher mask, avoiding an inset white tile.\nExpo supplies the native sizes and appearance catalog. Desktop assets remain\nunchanged. Rebuild the native app after changing these assets or configuration.\n\nThe Expo dynamic configuration keeps development on `com.tolaria.mob","isInternal":false,"tokens":10103,"sizeBytes":50064},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Claude Code compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n\n<!-- verity-memory:start -->\n## Project Memory\n\nThis project has a knowledge graph maintained at `.verity/memory/`. Before starting\nnon-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns\nthat may apply to the change you are about to make. Open specific node files via\nthe Read tool when the title or scope suggests relevance.\n\nThe graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are\nsuperseded — ignore them unless investigating history.\n\nKnowledge the organization's other repositories learned is mirrored outside the\nrepo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).\nEach claim says where it came from; one that is wrong here is demoted for\neveryone with `verity memory demote <id> --reason \"…\"`.\n\n## Quality gate: accepted risks\n\nWhen the Verity pre-commit/pre-push gate FAILs, fix the findings — that is the\ndefault. Use `verity waive <pattern-id> --file <path> --reason \"…\"` ONLY to relay\na risk a human has explicitly accepted: a named code-review finding, an ADR, or\nthe user saying so in this conversation. The --reason must cite that source.\n\nNever waive on your own judgment, to get past a block, or pre-emptively. A waive\nbinds to the file's current bytes and voids automatically when the file changes,\nand every waive is recorded in the run ledger. For a pattern-level false positive\nuse `verity feedback finding <run-id> <pattern-id> false_positive` instead.\n\n## Post-task reflection\n\nWhen a task is complete (you've created a PR, the user says \"done\" or \"ship it\",\nor the work is clearly finished), **draft the reflection yourself** — 1–3\nconcrete things worth remembering (a decision, a gotcha, or a pattern), each\ncited with the files / PR / commands / error-signatures it came from. Skip\nentirely if nothing non-obvious happened — that judgement is the ONLY filter,\nbecause nothing reviews the reflection before it lands.\n\nThen record it straight away. There is no confirm step, in any environment:\n\n```bash\nverity reflect --user-input - --kind <kind> <<'VERITY_EOF_<random>'\n<your draft>\nVERITY_EOF_<random>\n```\n\nThe draft goes through the quoted heredoc, never inside `\"…\"` on the command\nline, where a `\"` or `$(…)` in it would run in the shell. Replace `<random>`\nwith 8 random letters and digits that you pick for this call, the same at both\nends. Text written before you picked them cannot end the heredoc early.\n\nAdd `--confirmed` ONLY when the user authored or dictated the words. Without\nit the node is stored as `source: agent` — Verity thought this, nobody checked\nit. With it, `source: user` at full confidence — a person stands behind this.\nNever claim the second for your own draft, however good it is.\n\n**Name the files in the text.** Verity scopes the reflection to the paths it\ncites, and a reflection that names no file in this repo is never retrieved for\na later review — it is recorded and then invisible. The command says so when it\nhappens; `--file-globs '<path or glob>'` is the fix when the prose cannot carry\nthe paths.\n\nThen tell the user, in one line, what you recorded and where: the command\nprints the node id, the path under `.verity/memory/`, and a dashboard link.\nThey did not agree to it in advance, so say it happened — editing or deleting\nthat file is how they correct it.\n\n> Durable, hand-curated guidance goes in the preserve region below (it survives\n> regeneration) or anywhere OUTSIDE these markers. Everything else between the\n> markers is tool-owned and overwritten on each run.\n\n## Housekeeping Turns\n\nWhen a turn will be pure housekeeping — pulling, installing dependencies,\nrebasing, a formatting sweep you are not authoring — declare it BEFORE doing it:\n\n```bash\nverity ignore --turn --agent --reason \"pulling latest before starting\"\n```\n\nThis skips the review for that turn, which saves the turn Verity would\notherwise spend saying it had nothing to say. Use `--for 30m` instead of\n`--turn` when a single piece of housekeeping spans several turns.\n\n**It is a claim about the turn, not a way to silence review.** The declaration\nis checked against what the turn actually did: if anything is authored — by you,\nby a subagent, or by a shell command that can write files — it voids, the review\nruns anyway, and the broken declaration is reported. So declare housekeeping you\nare about to do, never work you have already done, and never as a way to get past\na finding. Declarations are budgeted per session and every one is recorded with\nits reason.\n\n<!-- verity-memory:preserve -->\n<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. -->\nVerity is set up only on some machines (currently the agent-claude box). If the `verity` CLI or\n`.verity/memory/` is missing, skip the Verity steps above; `AGENTS.md` stays the source of truth.\n<!-- /verity-memory:preserve -->\n<!-- verity-memory:end -->\n","frontmatter":{"type":"Note","_organized":true},"isInternal":false,"tokens":1184,"sizeBytes":5047},{"name":"GEMINI.md","path":"GEMINI.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/GEMINI.md","title":"Gemini Agent & Antigravity Instructions","category":"gemini-rule","format":"markdown","content":"---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Gemini CLI compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n","frontmatter":{"type":"Note","_organized":true},"isInternal":false,"tokens":36,"sizeBytes":147},{"name":"create-adr.md","path":".claude/commands/create-adr.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/.claude/commands/create-adr.md","title":"Command: /create-adr","category":"command-prompt","format":"markdown","content":"# Create Architecture Decision Record\n\nUse this command when you need to document an architectural decision made during a task.\n\nInspired by [adr-tools](https://github.com/npryce/adr-tools) (Nygard format), adapted for Laputa's frontmatter-based note format.\n\n## When to use this\n\nCreate an ADR when your work involves any of these:\n- Choosing a storage strategy (vault vs app settings vs database)\n- Adding or removing a major dependency\n- Supporting a new platform or target\n- Introducing or removing a core abstraction\n- Making a cross-cutting decision that affects how future code should be written\n\nDo NOT create ADRs for: bug fixes, UI styling, refactors that preserve behavior, or test additions.\n\n## Creating a new ADR\n\n### 1. Find the next ID\n\n```bash\nls docs/adr/*.md | grep -oP '\\d{4}' | sort -n | tail -1 | xargs -I{} printf '%04d\\n' $(({} + 1))\n```\n\nIf no files exist yet, start at `0001`.\n\n### 2. Create the file\n\nFilename: `docs/adr/NNNN-short-kebab-title.md`\n\nTemplate:\n\n```markdown\n---\ntype: ADR\nid: \"NNNN\"\ntitle: \"Short decision title\"\nstatus: active\ndate: YYYY-MM-DD\n---\n\n## Context\n\nThe issue motivating this decision, and any context that influences or constrains it.\n\n## Decision\n\n**The change we're proposing or have agreed to implement.** State it clearly in one or two sentences — bold so it stands out.\n\n## Options considered\n\n- **Option A** (chosen): brief description — pros / cons\n- **Option B**: brief description — pros / cons\n- **Option C**: brief description — pros / cons\n\n## Consequences\n\nWhat becomes easier or harder as a result?\nWhat risks does this introduce that will need to be mitigated?\nWhat would trigger re-evaluation of this decision?\n\n## Advice\n\n*(optional)* Input received before making this decision — who was consulted, what they said.\nOmit this section if the decision was made without external input.\n```\n\n### 3. Update the index\n\nAdd a row to `docs/adr/README.md`:\n\n```markdown\n| [NNNN](NNNN-short-kebab-title.md) | Title | active |\n```\n\n### 4. Include in the same commit as the feature\n\n```bash\ngit add docs/adr/NNNN-*.md docs/adr/README.md\n# fold into the feature commit — do not create a separate commit just for the ADR\n```\n\n---\n\n## Superseding an existing ADR\n\nEquivalent of `adr new -s <N>` from adr-tools — do this in two steps:\n\n### Step 1: Mark the old ADR as superseded\n\nEdit the existing file — add `superseded_by` and update `status`:\n\n```yaml\n---\ntype: ADR\nid: \"000N\"\ntitle: \"Old decision title\"\nstatus: superseded          # ← change from active\nsuperseded_by: \"NNNN\"       # ← add this\ndate: YYYY-MM-DD\n---\n```\n\n**Never edit the content sections** of an active ADR — only the status metadata.\n\n### Step 2: Create the new ADR\n\nFollow the steps above. In the **Context** section, reference the superseded ADR:\n\n```markdown\n## Context\n\nSupersedes [ADR-000N](000N-old-title.md).\n\n[explain why the old decision no longer holds]\n```\n\n### Step 3: Update the README index\n\nChange the old row's status to `superseded`, add the new row.\n\n---\n\n## Best practices (from adr-tools / Nygard)\n\n- **One decision per ADR** — if you find yourself writing \"and also\", split it\n- **Write Decision first** — if you can't state it in 1-2 sentences, the decision is too vague\n- **Context is the \"why now\"** — what forced this decision to be made today?\n- **Consequences should include negatives** — a one-sided ADR is a red flag\n- **Committed = immutable** — once pushed, the content doesn't change; only status metadata does\n- **If in doubt, create one** — cheaper to have an unnecessary ADR than to lose context\n- Date = today's date, `YYYY-MM-DD`\n","isInternal":false,"tokens":935,"sizeBytes":3628},{"name":"laputa-done.md","path":".claude/commands/laputa-done.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/.claude/commands/laputa-done.md","title":"Command: /laputa-done","category":"command-prompt","format":"markdown","content":"# /laputa-done <task_id>\n\nMark a Laputa task as done: add completion comment, move to In Review, then self-dispatch the next task.\n\nRun this after Phase 1 (Playwright) and Phase 2 (native app QA) both pass **and `git push origin main` has succeeded**.\n\n⚠️ A task is NOT done until the push succeeds. If the push is blocked by the pre-push hook (clippy, tests, CodeScene, build):\n- Read the error\n- Fix it (never use `--no-verify`)\n- Commit the fix and push again\n- Repeat until push exits with code 0\n\n## Steps\n\n**1. Add completion comment to the task**\n\nSummarize what was done — this is the context Luca and Brian will read in Todoist:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"task_id\": \"$ARGUMENTS\",\n    \"content\": \"✅ Implementation complete.\\n\\n**What changed:** [brief summary of the implementation]\\n**ADR:** [if an ADR was created, reference it here; otherwise omit]\\n**Playwright:** all tests pass\\n**Native QA:** tested with pnpm tauri dev — [describe what was tested and what was observed]\"\n  }'\n```\n\n**2. Move task to In Review**\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/$ARGUMENTS/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjX33FF4Vj86M\"}'\n```\n\n**3. Pick the next task**\n\nRun `/laputa-next-task` to get the next task and start working on it immediately.\n\nIf there are no tasks, `/laputa-next-task` will wait 10 minutes and retry automatically. Do NOT exit — stay alive and let it loop.\n","isInternal":false,"tokens":459,"sizeBytes":1640},{"name":"laputa-next-task.md","path":".claude/commands/laputa-next-task.md","rawUrl":"https://raw.githubusercontent.com/refactoringhq/tolaria/HEAD/.claude/commands/laputa-next-task.md","title":"Command: /laputa-next-task","category":"command-prompt","format":"markdown","content":"# /laputa-next-task\n\nPick the next Laputa task from Todoist and move it to In Progress.\n\nPriority order: **To Rework** first, then **Open** (sorted by Todoist priority p1→p4).\n\n## Steps\n\n1. Fetch tasks from To Rework (`6g6QqvR9rRpvJWvv`), then Open (`6g3XjWR832hVHhCM`)\n2. **Sort by priority — this is mandatory.** Todoist returns tasks in arbitrary order. You must sort them yourself:\n   - Todoist priority field: `4` = p1 (urgent), `3` = p2, `2` = p3, `1` = p4\n   - Sort descending by `priority` field (4 first, 1 last)\n   - To Rework tasks always come before Open tasks regardless of priority\n   - **Never pick a p3/p4 task if a p1/p2 task exists in the same section**\n3. Take the first task from the sorted list\n4. Move it to In Progress (`6g3XjWjfmJFcGgHM`) via Todoist API:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/<task_id>/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjWjfmJFcGgHM\"}'\n```\n\n5. Add a \"started\" comment to the task:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"task_id\": \"<task_id>\", \"content\": \"🚀 Starting work. [Brief description of approach or what needs to be fixed]\"}'\n```\n\n6. Fetch the full task details (description, comments) from Todoist:\n\n```bash\ncurl -s \"https://api.todoist.com/api/v1/tasks/<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n\ncurl -s \"https://api.todoist.com/api/v1/comments?task_id=<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n```\n\n6. For To Rework tasks: read the ❌ QA failed comment — it tells you exactly what to fix\n7. Output: task ID, title, and full description so you can start working immediately\n\nIf no tasks are available in either section → wait 10 minutes and try again (loop forever):\n\n```bash\nwhile true; do\n  # ... check tasks ...\n  if no_tasks; then\n    sleep 600  # 10 minutes\n  else\n    break  # got a task, proceed\n  fi\ndone\n```\n\nDo NOT exit when there are no tasks. Keep looping until a task appears. This keeps Claude Code alive permanently — the watchdog is a safety net only, not the primary dispatcher.\n","isInternal":false,"tokens":664,"sizeBytes":2234}],"systemPromptSnippet":"<yakaai_skills repo=\"refactoringhq/tolaria\">\n<!-- File: AGENTS.md (Tokens: ~7829 | Category: root-instruction) -->\n# AGENTS.md — Tolaria App\n\n## 1. Development Process\n\n### Mandatory-rule exception protocol\n\nUse an exception only when a required service or analyzer remains unavailable after one retry, or when satisfying the rule is technically impossible without increasing security or data-loss risk. The repository owner is the sole approver. Before proceeding, record the blocked command, evidence, affected files, risk, compensating check, approver, and an expiry of at most 72 hours in the Linear issue or final handoff; review and remove the exception within seven days. A preference, deadline, or failing quality gate is not sufficient exception criteria.\n\n### Task tracking (Linear)\n\n**Linear is the only live queue (cutover 2026-10-15).** Desktop work lives in team Tolaria (`TOL`), project **Tolaria Desktop**; mobile work in **Tolaria Mobile**; web work in project Tolaria Web. Do not create or move Todoist tasks. The old Todoist Tolaria project is legacy (its Draft section is being triaged separately); a completed Todoist card commented \"Moved to Linear: TOL-n\" points to its Linear issue.\n\n**Linear** — workspace Refactoring, team Tolaria, issue key `TOL`.\n\n- Workflow: **Backlog** (not ready) → **Open** (ready for an agent) → **To Rework** (review failed: read the latest review comment and fix only that; pick these **before** Open) → **In Progress** (claimed) → **In Review** (pushed, awaiting Luca's QA) → **Done** / **Canceled** / **Duplicate**.\n- Claim an issue by moving it to In Progress **before** starting work. Never take another worker's In Progress issue; resume your own only when a prior claim comment identifies this worker and worktree. Re-read the issue after claiming and stop on a conflicting claim. Exception: follow the mandatory-rule exception protocol if Linear remains unavailable after one retry.\n- Labels: type `Bug` / `Feature` / `Improvement`; `mac-only` (leave for the Mac mini); `linux-ok`; `agent-blocked` (always with a comment saying why it is stuck and what would unblock it); `dubious` and `contributor-pr` (Backlog, awaiting Luca); Source group `Sentry` / `Canny` / `GitHub`. Exception: an unavailable Linear labeling service requires the same owner-approved exception protocol.\n- Reference the issue ID in commit messages, e.g. `fix: keep image width after restart (TOL-123)`.\n\n### Start working on a task\n\n**Before writing a single line of code:** inspect the configured CodeScene project's current Hotspot and Average Code Health and compare them with `.codescene-thresholds`. Then capture the file-level Code Health score for every existing code file you intend to edit. If the project is already below the threshold, **stop and refactor first** — find the worst files with the MCP, improve them, commit, then start the task. If the gate cannot be restored, stop and obtain explicit repository-owner direction before starting feature work.\n\n- Read task description and all comments fully\n- For To Rework: the ❌ QA failed comment tells you exactly what to fix\n- Check `docs/adr/` for relevant architecture decisions before structural choices\n- Check `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md` for relevant structural information\n- For UI tasks: study app visual language and components first. Prioritize reusing existing components, assets, and variables over recreating them.\n- If working on a Linear issue, add a comment: `🚀 Starting work on this task. [Brief description of approach]`\n\n### Commits & pushes\n\n- Local work may happen on `main`, in detached HEAD worktrees, or in other temporary local states. The production path is still direct-to-main: final verified work is pushed to `origin/main`, with no PR branch flow.\n- Mobile foundation is integrated into main. New mobile work follows the same direct-to-main path; the historical foundation-branch fast lane is not the current delivery workflow.\n- Commit every 20–30 min: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`\n- Pre-commit is a lightweight lint gate only. Pre-push runs the full check suite (build + tests + coverage + core Playwright smoke + CodeScene), preferably on three Chunk sidecar lanes for automatic test/coverage work: frontend lint/build/coverage, Rust coverage, and Playwright smoke. The goal is lower wall-clock time than local hooks while keeping each heavy gate isolated; keep local Playwright mainly for authoring, focused reproduction, or sidecar outages.\n- CircleCI is the authoritative outer-loop CI/CD system. `.circleci/config.yml` owns validation, native cross-platform release builds, GitHub Release publication, and Pages publication.\n- **A task is NOT done until `git push origin main` succeeds.** If the hook blocks: read the error, fix it (clippy, tests, CodeScene, build), commit the fix, push again. If a verified push is impossible, stop and obtain explicit repository-owner direction; do not use `--no-verify` without that recorded approval.\n\n### Mobile coding and release\n\nThe general coding, quality, localization, analytics, credentials and completion rules apply unchanged. Read the mobile sections of `docs/ARCHITECTURE.md` and `docs/ABSTRACTIONS.md`; ADRs 0247, 0263 and 0272 define OTA delivery and channel selection.\n\n- Reuse `apps/mobile/src/components/ui` native primitives and `apps/mobile/src/ui` wrappers, not desktop DOM controls. Keep phone/tablet behavior on the shared controller/repository boundary. Run `pnpm mobile:lint`, `pnpm mobile:typecheck`, `pnpm mobile:test` and focused mobile regressions as well as the normal main push gates; scoped mobile checks do not replace them.\n- Verify affected native behavior separately on iPhone, iPad and Android. Browser checks are not native acceptance. Record task-specific owner delegation of manual QA honestly; it does not waive automated gates. A locked Mac leaves affected checks pending while independent work continues.\n- QA is confined to owned disposable vaults/simulators; existing user apps, containers and vault data stay untouched.\n- CircleCI publishes mobile-related main pushes to **Alpha** after its required gates; desktop-only changes are excluded from mobile publication. Confirm the actual publication receipt, source SHA, runtime and update ID before claiming delivery. Native/configuration/dependency changes that alter the runtime require a compatible signed binary, not an incompatible OTA.\n- Use one external **Tolaria Beta** TestFlight group. Settings defaults to **Stable**, with Alpha opt-in; selected channel and running update identity are distinct. Legacy Alpha-native binaries can launch embedded Alpha before preferences load. Retain runtime matching, safe editor flush/restart and Sentry artifact verification.\n- **Stable promotion remains manual**, authorized explicitly by the owner and using the existing exact-tested-Alpha-artifact republish/approval workflow. Never rebuild from moving main or redirect Stable to moving Alpha. Do not submit Apple compliance declarations or grant new credential permissions without explicit owner authorization.\n\n### Hourly mobile queue routine\n\nKeep the scheduled prompt limited to queue orchestration; this file owns coding and release policy.\n\n1. Reuse the routine's attached worktree and inspect current work. Resume its own identified unfinished claim before taking another issue; keep execution serial under the claim protocol above.\n2. Otherwise paginate **Tolaria Mobile** To Rework, then Open; order by priority (unspecified last), then oldest creation time. Skip others' assigned/delegated work, `agent-blocked` issues and unmet dependencies. Do not pull Backlog or In Review into implementation. Follow the claim protocol above.\n3. Implement the selected issue under this file's rules. On a blocker, record the evidence and required owner action in Linear, preserve a safe checkpoint, and continue independent eligible work without mixing partial changes or extending exceptions.\n4. Put verified implementation In Review with its release record, not self-approved Done. Report meaningful milestones or actionable blockers; stay quiet when the queue or status is unchanged. Resume remaining work on the next hourly run. Keep only one active mobile queue routine.\n\n### TDD (mandatory)\n\nRed → Green → Refactor → Commit. One cycle per commit. For bugs: write failing regression test first, then fix. Exception: pure CSS/layout changes.\n\n**Test quality (Kent Beck's Desiderata):** Isolated · Deterministic · Fast · Behavioral · Structure-insensitive · Specific · Predictive. Fix flaky tests first. Prefer E2E over unit tests for user flows.\n\n### Localization (mandatory for UI copy)\n\nAll user-facing UI labels/copy must live in `src/lib/locales/en.json` and be translated into every target listed in `lara.yaml`. Exception: when the localization service remains unavailable after one retry, use the mandatory-rule exception protocol above and do not release untranslated copy without repository-owner approval. When adding or changing interface copy:\n\n```bash\npnpm l10n:translate\n```\n\nUse `pnpm l10n:translate:force` only when intentionally regenerating existing translations. Commit `src/lib/locales/*.json`, `lara.yaml`/`lara.lock` changes if produced, and verify placeholders/product names stayed intact.\n\n### Product analytics (mandatory for meaningful features)\n\nNew features should emit a PostHog event so we can see whether users actually discover and use them. Skip instrumentation when the change has no meaningful user action or when a dedicated event would create noise; record that reason in the completion comment. If the correct instrumentation remains unclear, obtain repository-owner direction before release. Use clear, stable event names, avoid PII or note content, and include only safe metadata that helps evaluate adoption and failures.\n\nWhen adding or changing a meaningful user-facing feature, include the event name(s) in the Linear completion comment alongside QA, docs, and code health. If intentionally not instrumenting a feature, explain why in the completion comment.\n\n### Code health (mandatory)\n\nPre-push compares **Hotspot Code Health** and **Average Code Health** with the exact numeric floors in `.codescene-thresholds` and fails when either score is lower. Pre-commit is lint-only; CodeScene remains mandatory through the file-level review rules below and the pre-push ratchet gate. Thresholds are a **ratchet** — only go up. When pre-push sees improved remote scores, it updates `.codescene-thresholds`, stages it, and stops so you can commit the new floor with normal verified hooks before pushing again. Exception: adding `// eslint-disable`, `#[allow(...)]`, `as any`, or another bypass requires the mandatory-rule exception protocol and repository-owner approval before the edit.\n\n**Release rule:** CodeScene is a before/after gate, not just a final score. Record the starting CodeScene state before edits and the final state after edits. If touched code gets worse, refactor before committing; if the comparison cannot be produced, stop and obtain explicit repository-owner direction.\n\nDo not edit `.codescene-thresholds` to lower the values. If the gate blocks you, improve the code; any proposed exception requires explicit repository-owner approval recorded before the edit.\n\n**CodeScene access order:** use CodeScene MCP tools if available. If MCP is unavailable, use the installed `cs` CLI for file-level review/delta work, and use the CodeScene API (`CODESCENE_PAT` + `CODESCENE_PROJECT_ID`) for project-wide Hotspot/Average threshold checks from `.codescene-thresholds`.\n\n**Before editing any existing code file:** capture its current file-level CodeScene score. After your edits, re-run the same file-level review and verify the score is higher. If the file already starts at `10.0`, it must remain `10.0`. Exception: if the score cannot be obtained after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before editing.\n\n**New files:** every new **scorable code file** must reach CodeScene score `10.0` before commit. If CodeScene reports `null` / \"no scorable code\" for a new file, it must still have zero CodeScene findings/warnings. If either requirement cannot be met, stop and obtain explicit repository-owner direction.\n\n**Before every commit:** run CodeScene file-level review on every touched or newly created code file and verify the rule above. Then run `mcp__codescene__pre_commit_code_health_safeguard` for the repository and do not commit unless its quality gates pass. **Boy Scout Rule:** every file you touch must leave with a higher score, unless it was already `10.0`, in which case it must stay `10.0`. If an analyzer or gate is unavailable, stop and obtain explicit repository-owner approval for a documented exception.\n\n**Before the final direct-to-main push:** run `mcp__codescene__analyze_change_set` with `base_ref=origin/main`. This is Tolaria's PR-preflight equivalent: every affected file must be improved or stable, and the overall quality gate must pass. Refactor and repeat if any file is degraded. Exception: if the analysis cannot complete after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before pushing.\n\n**If CodeScene gate blocks your push:** use `mcp__codescene__code_health_score` to find the worst file, refactor it, commit, push again. Do not wait for laputa-refactor because it is a background loop rather than a substitute for fixing your own regressions; if no in-scope refactor can restore the gate, stop and obtain explicit repository-owner direction.\n\n### Security scan with Codacy (mandatory)\n\nUse Codacy as a security and static-analysis gate before a task is considered releasable.\n\n- **Two required checks:** Codacy's local CLI/MCP analyzes the uncommitted working tree, while the Codacy dashboard also runs server-side tools after a commit is pushed. These are complementary gates. Report each count with its source; if either check is unavailable, stop and obtain explicit repository-owner approval instead of substituting one for the other.\n- **What counts as new code:** both a newly created code, test, script, or executable configuration file and any added code in an existing file. Scan the entire containing file, not only the added lines, because Codacy tools can report findings on unchanged lines or at file level after a change.\n- **Build the scan manifest:** before each Codacy check, enumerate added and modified tracked paths with `git diff --name-only --diff-filter=ACMR \"$(git merge-base HEAD origin/main)\" --` and untracked paths with `git ls-files --others --exclude-standard`. Classify every code, test, script, and executable configuration path as new or existing, record the manifest in the task evidence, and scan every path individually. Rebuild the manifest immediately before commit so no late-created file is omitted.\n- **Before editing existing code:** run a local file scan and record every finding for each code file you intend to touch. Also record that file's findings on Codacy's currently analyzed `main` commit using `codacy_list_files` to resolve the file and `codacy_get_file_issues` to enumerate all paginated results.\n- **Local file scan:** use `mcp__codacy__codacy_cli_analyze` with the repository's absolute `rootPath` and an explicit `file`, one touched/new file at a time. If the MCP is unavailable or fails, run `LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 .codacy/cli.sh analyze <path> --format sarif --output /tmp/codacy-<slug>.sarif`. Omit `--tool` so every locally configured analyzer runs. Explicitly inspect every `runs[].results[]` entry in the SARIF output.\n- **Analyzer-parity gate:** before creating or modifying code, call `codacy_list_repository_tools` and record the enabled dashboard analyzer name, UUID/generation, configuration mode, and language coverage. Compare that roster with the `runs[].tool.driver` entries produced locally for every language in the scan manifest. Tool names alone are not sufficient: deprecated ESLint/ESLint8 and current ESLint9 are different analyzers, and a local run of Lizard, Opengrep, and Trivy does not prove that dashboard ESLint will be clean. For TypeScript/JavaScript, the passing roster must include the current analyzer that honors `eslint.config.js`, plus every other enabled relevant dashboard tool. If the intended analyzer is disabled, a deprecated analyzer is enabled, or any enabled dashboard analyzer has no equivalent successful local run, analyzer parity fails: stop, correct the Codacy tool configuration, trigger a full reanalysis, and verify parity before code work. A repository-owner-approved exception is required if configuration access is unavailable.\n- **Approved analyzer-parity exception (Biome):** Biome is enabled on the Codacy dashboard but missing from the local Codacy CLI. Biome: covered locally by the repo's own Biome (`npx biome check <changed files>`, or the repo's existing Biome run in `pnpm codacy:gate`); approved by Luca 2026-10-10. This is a standing exception recorded here, so it needs no per-task re-approval: record that `npx biome check` run as the local Biome row in the parity and per-file evidence. It covers only the missing local Biome run; any Biome diagnostic on changed files still fails the gate, and every other analyzer must still match.\n- **Per-file analyzer evidence:** for every new code file, record one result row per expected analyzer with the exact file path, analyzer identity, completion status, and finding counts split into Critical/High/Medium/Minor/Info/unclassified. Missing rows, a skipped/untracked target, an analyzer crash, or an analyzer that silently omits the language is a failed scan, never a zero. If the CLI requires Git discovery, add the file to the index normally before the final scan; do not rely on a directory scan to discover an untracked file.\n- **Fail closed on partial scans:** a zero process exit code is not enough. Confirm the expected relevant tools produced SARIF runs and inspect scan logs for crashes, encoding failures, skipped target files, partial analysis, or missing tool output. If any relevant analyzer did not complete, the scan did not pass.\n- **Approved analyzer-coverage exception (unsupported or excluded files):** if Codacy or CodeScene does not support a file type, or excludes the path from analysis (for example test paths, `.husky/*` hook scripts), an agent does not need a pre-edit baseline or a post-edit score from that tool to edit the file. Record \"not analyzed by <tool>: <unsupported|excluded>\" as that tool's row in the parity/per-file evidence. Likewise, when an enabled dashboard analyzer has no local equivalent for a file type (for example Jackson Linter for `src/lib/locales/*.json`), cover it locally with the repo's own check (`node scripts/validate-locales.mjs` plus a JSON parse for locale catalogs) and record that run. Approved by Luca 2026-10-13; standing, no per-task re-approval. Every other gate still applies: the other tool's baseline and Boy Scout rule where it does analyze the file, zero new findings, tests, coverage, pre-push hooks and post-push verification.\n- **Zero-new-findings rule:** added or modified code must introduce no Codacy finding at any severity, including Info/Minor, Warning/Medium, High/Error/Critical, unclassified findings, and findings reported on unchanged lines or at file level because of the change. If this cannot be achieved, stop and obtain explicit repository-owner approval for a documented exception.\n- **New code — absolute zero rule:** every new code file and every file containing newly added code must be scanned explicitly after creation or modification and must have exactly zero findings attributable to the new code at every severity. A newly created file must have exactly zero findings in the whole file. For added code in an existing file, any finding on an added line, caused by an added symbol or dependency, or newly appearing at file level fails the gate. `0 Critical + 0 High + 0 Medium + 0 Minor + 0 Info + 0 unclassified` is the only passing result. If a scanner cannot prove this, stop and obtain explicit repository-owner approval before proceeding.\n- **Boy Scout Rule for existing files:** after editing, every touched file must have fewer local findings than its recorded baseline; a zero-finding file must stay at zero. Fix every existing Critical/High finding in a touched file; if this cannot be achieved, stop and obtain explicit repository-owner approval for the documented exception.\n- **Before every commit:** re-scan every touched/new code file individually and compare the complete SARIF results with its recorded baseline. Do not rely on added-line filtering, repository totals, or `pnpm codacy:gate` alone; if an individual scan cannot complete, stop and obtain explicit repository-owner approval.\n- **Post-push dashboard verification:** after `git push origin main`, wait until `codacy_get_repository_with_analysis` reports the exact pushed SHA as `lastAnalysedCommit`. Re-read the enabled-tool roster, paginate `codacy_list_repository_issues` to completion, and filter by each exact manifest path; use file-level MCP queries as supporting detail, not as a substitute for the complete repository issue list because newly created files may not yet resolve through file lookup. Absence from the issue list counts as zero only when the exact SHA is analyzed, the analyzer roster still matches the pre-edit parity record, and the repository reports no analyzer problem. Every new code file must have zero dashboard findings at every severity from every enabled analyzer; every existing touched file must have no new finding and must have fewer findings than its pre-edit dashboard baseline (or remain at zero). If the dashboard reports a finding—even one from a deprecated or misconfigured analyzer—the task remains unfinished until the file is fixed or the analyzer configuration is corrected and a replacement exact SHA is fully reanalyzed. Exception: if verification remains unavailable after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- **Repository-wide dashboard counts:** use `codacy_get_repository_with_analysis` for the total and `codacy_list_repository_issues` with full pagination for severity/tool breakdowns. Derive the provider/organization/repository from the Git remote without printing credential-bearing remote URLs; for this repository use `gh` / `refactoringhq` / `tolaria` and branch `main`.\n- **Escalation:** if a scanner is unavailable or a finding is demonstrably false, stop and obtain explicit repository-owner approval recorded in the completion comment. Rule suppression requires that same explicit approval and documentation.\n- `pnpm codacy:gate` is a required fail-closed added-line safety net in pre-push and CI; it does not replace the touched-file before/after check.\n\n### Check suite (runs on every push)\n```bash\npnpm lint && npx tsc --noEmit && pnpm test && pnpm test:coverage  # frontend ≥71%\ncargo test && cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85\n```\n\nCoverage is a release gate, not a vanity metric:\n- Frontend coverage must stay ≥71%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- Rust line coverage must stay ≥85%. Exception: if coverage cannot be measured after one retry because the coverage service is unavailable, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n- For bug fixes, add a regression test when practical.\n- For new behavior, add targeted coverage close to the changed code; do not rely only on broad E2E coverage.\n\n### UI and native QA\n\n**Phase 1 — Playwright (only for core user flows):**\n\nWrite Playwright test in `tests/smoke/<slug>.spec.ts` when the feature touches vault open, note create/save/delete, search, wikilink navigation, git commit/push, or conflict resolution. Tag a test with `@smoke` when it protects one of those core pre-push workflows; keep cosmetic or mock-heavy checks in the full regression lane. Prefer `.chunk/run-playwright-smoke.sh` on a Chunk sidecar for the curated smoke lane because local Playwright is expensive; keep `pnpm playwright:smoke` available for focused local reproduction. The curated smoke suite must stay under **5 minutes** when sharded on sidecars. Exception: if a platform outage prevents the smoke test from running after one retry, use the mandatory-rule exception protocol and obtain repository-owner approval before release.\n\n```bash\npnpm dev --port 5201 &\nsleep 3\nBASE_URL=\"http://localhost:5201\" npx playwright test tests/smoke/<slug>.spec.ts\n```\n\n**Phase 2 — Native app QA (macOS):**\n\n> On a Linux agent box, skip this Phase 2 block and follow **Linux agent boxes (Hetzner / remote)** below instead.\n\n```bash\npnpm tauri dev &\nsleep 10\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh laputa\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/qa-native.png\n```\n\nUse computer-use/browser-control interaction for native UI QA when either tool is present in the current environment: click, hover, drag, select, scroll, and type the way a real user would with the mouse and trackpad. For every UI feature, test the primary mouse-driven path first, then verify each keyboard shortcut or keyboard-first workflow implemented or modified by the task. If neither interaction tool is present, run the scripted focus, screenshot, and shortcut checks and document that limitation.\n\nUse `osascript` for app focus, keyboard shortcuts, and keyboard-specific checks. **⚠️ WKWebView:** if an `osascript keystroke` fails to enter editor text, use computer use for the native editor interaction and rely on Playwright for deterministic text-input coverage. Write the result as a Linear comment (✅ or ❌), or in the final handoff when no Linear issue exists.\n\n### Linux agent boxes (Hetzner / remote)\n\nThese rules apply when the agent runs on a Linux box (no macOS desktop, no `osascript`, no `~/.openclaw`). The macOS instructions above stay in force for the Mac mini; do not remove or weaken them.\n\n**Mac-native tasks stay with the Mac mini.** Do not claim tasks labeled `mac-only`, or tasks that clearly need macOS/Windows-native surfaces: updater / auto-update, Homebrew, macOS menus and Cmd/Option shortcut wiring, native file/open/save dialogs, signing/notarization, Windows installers. Leave them in the queue for the Mac mini.\n\n**QA on Linux = the Linux Tolaria build with a throwaway vault.** There is no macOS native QA on these boxes. Build the Linux app (`CI=true pnpm tauri build --debug --no-bundle`) and drive it under Xvfb with a throwaway copy of `demo-vault-v2/` — on the agent boxes use the installed `tolaria-qa` CLI (`tolaria-qa help`: `start`, `click`, `key ctrl+…`, `xtype`, `shot`, `smoke`, `stop`). Never open, create, or modify `~/Laputa` or any real user vault. Test the primary mouse path first, then each keyboard shortcut you added or changed (`Ctrl` on Linux where macOS uses `Cmd`). Phase 1 Playwright still applies. Record QA (✅/❌, what was clicked/typed, screenshots) in the task comment as usual.\n\n**Before pushing to main:** the full check suite passes; then `git fetch origin && git rebase origin/main` (stop on conflicts you cannot resolve safely), re-run the checks (including the Linux build and a Linux app smoke) on the rebased HEAD, then `git push origin main` so the pre-push hook runs against the latest `origin/main`. Never `--no-verify` without a recorded repository-owner exception.\n\n### Release-readiness checklist\n\nBefore pushing or moving a task to In Review, verify the release gates and add a **completion comment** to the Linear issue. Exception: when the work has no Linear issue, put the identical evidence in the final handoff and identify it as the release record. The record must include:\n\n- What was implemented (a few lines covering logic and UX/UI).\n- QA: what was tested and how (Playwright / native screenshot / osascript on macOS; Linux app driven with a throwaway vault on Linux agent boxes).\n- Tests/coverage: commands run and final coverage result.\n- CodeScene: before/after touched-file checks, the pre-commit safeguard verdict, the final `origin/main` change-set verdict, plus final Hotspot and Average scores after push; every gate must pass `.codescene-thresholds`, or the record must include explicit repository-owner approval for a documented exception.\n- Coverage commands passed (`pnpm test:coverage` and `cargo llvm-cov ... --fail-under-lines 85`) or the change is docs-only.\n- Codacy: the final scan manifest; local before/after findings for every touched file; confirmation that every relevant local analyzer completed; and post-push dashboard findings after Codacy analyzed the exact pushed SHA. Confirm fewer findings in existing files (or zero stayed zero), exactly zero local and dashboard findings in every new code file, and zero findings attributable to added code in existing files at every severity.\n- Localization: any user-facing copy lives in `src/lib/locales/en.json`, `pnpm l10n:translate` was run, and `pnpm l10n:validate` passes. If no copy changed, say “Localization: no UI copy changes”.\n- PostHog: meaningful new user actions/events are instrumented with safe metadata; noisy/minor changes explicitly say “PostHog: no event needed because …”.\n- Refactoring: any files refactored to meet the CodeScene gate, or \"none needed\".\n- ADRs: any new/updated ADRs, or \"none\".\n- Docs: any updated docs (`docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, etc.), or \"none\".\n- Demo vault dirt checked: `git status --short -- demo-vault demo-vault-v2` is empty unless fixture changes are intentional.\n\n### ADRs & docs\n\nADRs live in `docs/adr/`. Create one in the same commit as the code. Preserve existing ADRs by creating a new one that supersedes them; editing an existing ADR requires an explicit repository-owner request. Use `/create-adr`. **When:** new dependency, storage strategy, platform target, core abstraction, cross-cutting pattern. **Not for:** bug fixes, styling, refactors.\n\nAfter any Tauri command, new component/hook, data model change, or new integration: update `docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, and/or `docs/GETTING-STARTED.md` in the same commit.\n\n---\n\n## 2. Product Rules\n\n### Demo vault hygiene (`demo-vault/`, `demo-vault-v2/`)\n\nDefault to `demo-vault-v2/` for testing.\n\n- Treat `demo-vault/` and `demo-vault-v2/` as disposable QA fixtures unless the task explicitly changes demo content.\n- If you create untracked notes, attachments, or other temporary files there for testing, delete them before the task is complete.\n- If you modify tracked demo-vault files only to test or QA behavior, revert those edits before the final commit.\n- Before declaring a task done, make sure `git status --short -- demo-vault demo-vault-v2` is empty unless demo fixture changes are part of the task.\n- If a fresh run starts and the only local dirt is inside `demo-vault/` or `demo-vault-v2/`, clean those paths first and continue. That case is recoverable QA residue, not a blocker.\n\n### User vault (`~/Laputa/`)\n\nDefault to `demo-vault-v2/`. If you must use `~/Laputa/` for testing:\n- Do not commit or push test notes to the remote vault unless the repository owner explicitly identifies that content as an intended deliverable.\n- **Delete all test notes from disk** when done — do not leave untitled or temporary notes on the filesystem. Run `cd ~/Laputa && git checkout -- . && git clean -fd` to restore the vault to its last committed state.\n- **Rationale:** test notes pollute the local vault over time, making it a collection of nonsensical untitled files. Keep the vault clean on disk as well as on the remote; any intended fixture exception requires explicit repository-owner approval.\n\n### UI components — mandatory rules\n\nUse shadcn/ui components for user-facing interactive elements instead of raw HTML form elements (`<input>`, `<select>`, `<button>`, native `<input type=\"date\">`, etc.). If no suitable shadcn or existing app component exists, stop and obtain explicit repository-owner design approval before introducing a raw control.\n\n| Need | Use |\n|---|---|\n| Text input | `Input` from shadcn/ui |\n| Dropdown/select | `Select` from shadcn/ui |\n| Date picker | `Calendar` + `Popover` from shadcn/ui (NOT native `<input type=\"date\">`) |\n| Button | `Button` from shadcn/ui |\n| Autocomplete/combobox | Reuse existing combobox components from the app (check `src/components/`) |\n| Wikilink picker | Reuse the wikilink autocomplete component already used in the editor and Properties panel |\n| Emoji picker | Reuse the emoji picker component already used for note/type icons |\n| Color picker | Reuse the color swatch picker used for type customization |\n| Toggle/switch | `Switch` or `ToggleGroup` from shadcn/ui |\n| Dialog/modal | `Dialog` from shadcn/ui |\n\n**Component search trigger:** when the requested interaction does not map to a component in the table above, search `src/components/` by the interaction name and ARIA role before building a component. **Visual language:** new UI must follow Tolaria's existing components and design tokens; a deliberate exception requires explicit repository-owner design approval recorded through the mandatory-rule exception protocol.\n\n---\n\n## 3. Reference\n\n### Release notes\n\nRelease notes live in `release-notes/<tag>.md` and are published to GitHub Releases and the public website.\n\n**Format:**\n- Every release note must start with frontmatter; exceptions require the mandatory-rule exception protocol:\n  ```markdown\n  ---\n  type: Note\n  _organized: true\n  ---\n  ```\n- After frontmatter, write the user-facing content (features, improvements, fixes)\n- Use clear headings: `## New Features`, `## Improvements`, `## Stability and Fixes`\n- Use emojis and **bold** feature names for scanability\n- Write for end users, not developers\n\n**Frontmatter handling:**\n- The vault displays release notes as organized `Note` types\n- GitHub Release bodies and website copies automatically strip frontmatter via `scripts/strip-frontmatter.mjs`\n- Never expose frontmatter in user-facing surfaces\n\n**Creating a new release note:**\n```bash\ncat > release-notes/<tag>.md << 'EOF'\n---\ntype: Note\n_organized: true\n---\n\n## New Features\n\n- 🎉 **Feature name** — Description.\n\n## Improvements\n\n- ✨ **Enhancement** — Description.\nEOF\n```\n\nThe frontmatter ensures release notes are already organized when the Tolaria repo is opened as a vault, avoiding inbox clutter.\n\n### macOS / Tauri gotchas\n\n- `Option+N` → special chars on macOS. Use `e.code` or `Cmd+N`\n- Tauri menu accelerators: `MenuItemBuilder::new(label).accelerator(\"CmdOrCtrl+1\")`\n- `app.set_menu()` replaces the ENTIRE menu bar — include all submenus\n- `mock-tauri.ts` silently swallows Tauri calls — not a substitute for native testing\n\n### QA scripts\n\n```bash\nbash ~/.openclaw/skills/tolaria-qa/scripts/focus-app.sh Tolaria\nbash ~/.openclaw/skills/tolaria-qa/scripts/screenshot.sh /tmp/out.png\nbash ~/.openclaw/skills/tolaria-qa/scripts/shortcut.sh \"command\" \"s\"\n```\n\n### Diagrams\n\nPrefer Mermaid (`flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram-v2`). ASCII only for spatial wireframe layouts.\n\n## Stuck or blocked tasks\n\nWhenever an agent sets the `agent-blocked` label, moves a task back to Open, or otherwise leaves a task stuck, it must ALWAYS add a task comment explaining why: the exact blocker, the evidence (command/log path), and what decision or action would unblock it. Never leave a task blocked without that comment. Exception: a Linear outage after one retry requires the identical evidence in the final handoff and repository-owner approval under the mandatory-rule exception protocol.\n\n<!-- File: docs/ARCHITECTURE.md (Tokens: ~10103 | Category: project-spec) -->\n# Architecture\n\n## Desktop Note Save Transport\n\n`persistContent` sends native desktop saves through `save_note_content_bytes`\nfor notes over 256 Ki characters, using raw UTF-8 content and a base64 UTF-8 `x-tolaria-note-path` header. The\ncommand rejects malformed requests and delegates to `save_note_content`, retaining\nactive-vault path validation and the existing off-thread atomic writer. This\nbounds the JSON note payload and avoids a full JSON copy of embedded image payloads; memory remains proportional\nto note length. Browser fixtures retain their JSON save API. See ADR 0278.\n\nTolaria is a personal knowledge and life management desktop app. It reads a vault of markdown files with YAML frontmatter and presents them in a four-panel UI inspired by Bear Notes.\n\n## Design Principles\n\n### Filesystem as the single source of truth\n\nThe vault is a folder of plain markdown files. The app never owns the data — it only reads and writes files. The cache, React state, and any in-memory representation are always derived from the filesystem and must be reconstructible by deleting them. When in doubt, the file on disk wins.\n\n### Convention over configuration\n\nTolaria is opinionated. Standard field names (`type:`, `status:`, `url:`, `Workspace:`, `belongs_to:`, `related_to:`, `has:`, `start_date:`, `end_date:`) have well-defined meanings and trigger specific UI behavior — without any setup. Relationship defaults are stored in snake_case on disk and humanized in the UI. This is not convention *instead of* configuration: users can override defaults via config files in their vault (e.g. `config/relations.md`, `config/semantic-properties.md`). But the defaults work out of the box, and most users never need to touch them.\n\nThis principle directly serves AI-readability: the more structure comes from shared conventions rather than per-user custom configurations, the easier it is for an AI agent to understand and navigate the vault correctly — without needing bespoke instructions for every setup.\n\n### Where to store state: vault vs. app settings\n\nWhen deciding where to persist a piece of data, ask: **\"Would the user want this to follow them across all their Tolaria installations — other devices, future platforms (tablet, web)?\"**\n\n| Follows the vault | Stays with the installation |\n|-------------------|-----------------------------|\n| Type icon, type color | Editor zoom level |\n| Pinned properties per type | API keys (OpenAI, Google) |\n| Sidebar label overrides | Auto-sync interval |\n| Property display order | Window size / position |\n| Per-note `_width` rich-editor width override | Default rich-editor note width |\n| Applicable vault or parent-repository `.gitignore` patterns | Whether this installation hides Gitignored files |\n| N/A | Whether this installation shows Git features |\n| N/A | Git executable provider (`native` vs WSL2 distribution) |\n| Per-vault All Notes note-list column overrides | All Notes PDF/image/unsupported file visibility |\n| N/A | Per-vault Git setup prompt opt-out |\n| Type `_sidebar_label` overrides | Whether this installation auto-pluralizes type labels |\n| N/A | Registered workspace labels, aliases, mount state, and default new-note destination |\n| N/A | Mobile AutoGit, appearance, interface scale and analytics consent |\n| Any user-visible customization of how content is organized or displayed | Any machine-specific or credential-type setting |\n\n**Rule:** If the information is about *how the content is structured or presented* and the user would expect it to be consistent wherever they open their vault, store it in the vault (frontmatter of the relevant note, using the `_field` underscore convention for system properties). If it's about *this specific installation of the app*, store it in app config JSON under `$XDG_CONFIG_HOME/com.tolaria.app/` (defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms), or in localStorage for renderer-only transient layout state. The native resolver keeps XDG as the preferred root when the current user can write the target; if that root is not writable, it uses the platform config root instead and reads that writable fallback before stale XDG files. See [ADR-0177](./adr/0177-writable-fallback-for-app-config.md).\n\nMobile installation preferences use the app-owned document configuration\ndirectory with validated pending-file publication and backup recovery (ADR 0212).\nManaged Git vaults retain the existing save/operation queues. Manual sync requires\nan explicit local commit; opt-in AutoGit schedules foreground idle checkpoints\nand synchronization without promising background execution.\n\nMobile Git merges validate synthesized frontmatter and saved-view/configuration\nblobs before publishing the proposed tree (ADR 0220). Invalid metadata leaves\nHEAD, index and working files untouched for explicit conflict resolution.\nGit sync has an independent operation mutex and uses short editor/write barriers\naround checkpoints and incoming checkout, not HTTP waits (ADR 0221). Temporary\nfailures retry with bounded backoff, with Expo network reconnection as a retry\nhint. Authentication and conflict failures require attention.\nInstallation-local sync history is keyed by managed vault ID and published with\nbackup recovery (ADR 0222). It records successful remote/provider operations\nseparately from local commits and pending edits, without storing user content.\nThe same ledger keeps installation-local, revisioned paths for pending note dots\n(ADR 0271). A successful checkpoint acknowledges only the captured revisions;\nedits made while transport is running remain pending. These hints never replace\nfresh Git/content comparisons or authorize checkout.\nNative conflict review shares local/remote/keep-both choices across Git and linked\nfolders (ADR 0223). Git retains both parents; coordinated folder publication\nretains private original snapshots and an interruption journal. Comparisons are\nversion-checked again before applying any choices.\n\nNew iOS folder selections retain a Files-provider bookmark alongside an offline\nworking copy (ADR 0217). Native, coordinated three-way reconciliation writes\nlocal edits back and imports external changes; divergent versions stop for\nresolution. This is independent of Git. Provider delivery, including iCloud's\ncross-device upload, remains the provider's responsibility. Existing copy-only\nvaults are never silently attached to an original folder.\n\nExamples:\n- ✅ Vault: `_pinned_properties` in a Type note (every device should show the same pinned properties)\n- ✅ Vault: `_icon: shapes` in a Type note (icon is part of the type's identity)\n- ✅ Vault: `_width: wide` in a note that already has frontmatter (per-note reading/editing preference)\n- ✅ App settings: `zoom: 1.3` (machine-specific preference)\n- ✅ App settings: `ui_language: \"zh-CN\"` (installation-specific UI language)\n- ✅ App settings: `note_width_mode: \"wide\"` (installation-specific default for notes without an override)\n- ✅ App settings: `date_display_format: \"friendly\"` (installation-specific date rendering preference)\n- ✅ App settings: `sidebar_type_pluralization_enabled: false` (installation-specific sidebar label preference)\n- ✅ App settings: `all_notes_show_images: true` (installation-specific All Notes file-category visibility)\n- ✅ App settings: `git_provider: \"wsl\"` plus `git_wsl_distro: \"Ubuntu\"` (machine-specific Git executable location)\n\n### No hardcoded exceptions\n\nNo field names, folder paths, or vault-specific values should be hardcoded in the application source code. What can be a convention should be a convention. What needs to be configurable should live in a file. Relationship fields are detected dynamically by checking whether values contain `[[wikilinks]]` — no hardcoded field name lists.\n\n### AI-first knowledge graph\n\nNotes are not just documents — they are nodes in a structured graph of people, projects, events, responsibilities, and ideas. Every design decision should ask: \"Does this make the knowledge graph easier for a human *and* an AI to navigate?\" Conventions that are legible to both are better than conventions that are legible only to one.\n\n### Three representations, one authority\n\nVault data exists in three forms simultaneously:\n1. **Filesystem** — the `.md` files on disk. This is the single source of truth.\n2. **Cache** — `~/.laputa/cache/<hash>.json`, an index for fast startup. Always reconstructible from the filesystem.\n3. **React state** — the in-memory `VaultEntry[]` during a session. Always derived from the cache or filesystem.\n\nThese must never diverge permanently. If they do, the filesystem wins and the cache/state are rebuilt.\n\n```mermaid\nflowchart LR\n    FS[\"🗂️ Filesystem\\n.md files on disk\\n(source of truth)\"]\n    Cache[\"⚡ Cache\\n~/.laputa/cache/\\n(fast startup index)\"]\n    RS[\"⚛️ React State\\nVaultEntry[]\\n(in-memory session)\"]\n\n    FS -->|\"scan_vault_cached()\"| Cache\n    Cache -->|\"useVaultLoader on load\"| RS\n    FS -->|\"reload_vault (full rescan)\"| RS\n    RS -.->|\"write via Tauri IPC first\"| FS\n\n    style FS fill:#d4edda,stroke:#28a745,color:#000\n    style Cache fill:#fff3cd,stroke:#ffc107,color:#000\n    style RS fill:#cce5ff,stroke:#004085,color:#000\n```\n\n#### Ownership rules\n\n| Layer | Owner | Writes to | Reads from |\n|-------|-------|-----------|------------|\n| Filesystem | Tauri Rust commands (`save_note_content`, `update_frontmatter`, etc.) | Disk | — |\n| Cache | `scan_vault_cached()` in `vault/cache.rs` | `~/.laputa/cache/` | Filesystem + git diff |\n| React state | `useVaultLoader` + `useEntryActions` + `useNoteActions` | In-memory `entries` | Cache (on load), filesystem (on reload) |\n\n#### Invariants\n\n1. **Disk-first writes**: All functions that change vault data must write to disk (via Tauri IPC) *before* updating React state. This ensures that if the disk write fails, React state remains consistent with what's actually on disk.\n2. **Optimistic UI with rollback**: Where responsiveness matters (e.g. `persistOptimistic` in `useNoteCreation`), state may update before disk confirmation — but a failure callback must revert the optimistic state.\n3. **No orphan state updates**: Never call `updateEntry()` before the corresponding `handleUpdateFrontmatter()` or `handleDeleteProperty()` has resolved. Type metadata actions in `useEntryActions` follow this rule — create the missing type document if needed, write frontmatter first, then update state. If missing type creation collides with an existing note filename, the action stops after the existing creation toast instead of applying orphaned state.\n4. **Recovery via reload**: If state ever diverges from disk (crash, external edit, race condition), `Reload Vault` (Cmd+K → \"Reload Vault\") does a full filesystem rescan via the `reload_vault` Tauri command, atomically replacing the cache and all React state. The `reload_vault_entry` command can re-read a single file.\n5. **Cache is disposable and crash-safe**: A full reload keeps the last valid snapshot readable while it builds a replacement, then swaps the new cache into place only after the scan completes. A crash during refresh therefore cannot turn the next startup into a cache-miss scan. The cache remains derived from the filesystem and can always be deleted and rebuilt.\n6. **Visibility filters are command-boundary concerns**: Gitignored-content visibility is applied after scanning/caching, before entries, folders, or search results reach React. The cache remains complete so toggling the setting can show ignored content again without rebuilding a different cache shape. Large folder filtering runs on the blocking Tokio pool and drains `git check-ignore` output while feeding stdin so broad ignore matches cannot freeze the native UI thread.\n\n#### External Change Detection\n\nEvery full App window registers its active vault roots through `start_vault_watcher` / `stop_vault_watcher` (`src-tauri/src/vault_watcher.rs`, backed by Rust `notify`). The backend reuses one native watcher per shared root, tracks owning window labels, and removes a watcher only after its final owner unregisters. Watchers emit `vault-changed` events for content paths and ignore churn from `.git/`, `node_modules/`, temp files, and `.tolaria-rename-txn`. `useVaultWatcher` batches those events and suppresses recent app-owned saves. The main window sends remaining paths through `refreshPulledVaultState()` so folders, saved views, note-list state, and clean active-editor content refresh under the ADR-0135 unsaved-edit rules. Secondary note windows reload known changed entries individually, preserving the full graph while avoiding full scans; deleted, moved, unknown, or otherwise non-reloadable paths fall back to the shared full reconciler. A same-path replacement keeps the active tab mounted, and a frontmatter-only replacement keeps the current BlockNote document, caret, focus, and scroll position. Content changes still replace the clean editor document, while a true move or deletion clears the obsolete tab; when a document replacement starts from a focused editor, the app dispatches a post-replacement editor focus request. `useVaultLoader.isReloading` drives the status-bar reload spinner for both manual and full watcher-triggered reloads.\n\nWindow focus also requests Git-based external rename detection for the active vault. The shared `commands::blocking` boundary schedules synchronous command work on Tokio's blocking pool and converts join failures to string errors, retaining contextual labels where needed. The `detect_renames` Tauri command uses this boundary for its filesystem and Git subprocess work so a slow repository cannot stall the native event loop while the user resumes typing.\n\n#### Progressive Vault Loading\n\nVault opening waits for the persisted vault registry before selecting a path, so it never scans the legacy default vault on the way to the active workspace. On a warm start, `read_vault_snapshot` deserializes a version- and path-valid active-vault cache without Git, per-entry filesystem checks, sorting, or cache replacement; command-boundary visibility filtering can remove Gitignored entries without discarding that snapshot. React installs the provisional graph and clears `isLoading`, then `list_vault` reconciles Git changes and missing files in the background. Invalid or missing snapshots and forced reloads use the authoritative scan path. Clean same-commit reconciliation avoids both the full Git-date scan and an identical cache rewrite. Folders and saved views load independently; secondary workspaces start only after the active graph is usable and hydrate one at a time. Nested mounts derive their entries from a loaded ancestor instead of scanning the same subtree again. BlockNote and editor-only styles remain outside the initial shell chunk and prewarm after its first commit; selecting a note requests them immediately.\n\nStartup performance is measured end to end with ordered, path-free milestones spanning renderer initialization, persisted settings, vault selection, snapshot hydration, interactivity, editor loading, and background reconciliation. Each milestone records both renderer-relative and native-process elapsed time; `TOLARIA_STARTUP_TRACE=1` writes the same JSON events to stderr for repeatable native measurements without adding UI. Git repository probing runs on the blocking Tokio pool, and missing Git-related environment variables are resolved with one login-shell invocation rather than one process per variable. See [ADR-0166](./adr/0166-snapshot-first-progressive-vault-startup.md) and [ADR-0170](./adr/0170-measurable-crash-safe-startup.md).\n\nLarge-vault reproduction and keyboard QA steps live in [LARGE-VAULT-LOADING-QA.md](./LARGE-VAULT-LOADING-QA.md).\n\n#### Mounted Workspaces\n\nThe registered vault list can act as a mounted-workspace set. `useVaultSwitcher` persists each workspace's installation-local identity (`label`, stable `alias`, color, mount flag) and the default destination for newly created notes in `vaults.json` under Tolaria's app config directory (`$XDG_CONFIG_HOME/com.tolaria.app/`, defaulting to `$HOME/.config/com.tolaria.app/` on Unix platforms). If the native app cannot write that XDG target, it falls back to the platform config directory for both the write and the first subsequent read. `useVaultLoader` scans every available mounted workspace and annotates each `VaultEntry` with provenance before React consumes the combined graph. The default workspace is the write target for new notes and Type documents; it is not the only active vault when multiple workspaces are enabled.\n\nExternal MCP clients share that registry boundary. `attach_vault` registers an existing canonical directory without initializing Git, while `clone_vault` uses non-interactive system Git in a temporary sibling and publishes the destination only after clone success. Both preserve the current active vault, add the new root to the calling MCP process immediately, and broadcast a registry-change intent so running renderers reload mounted-workspace metadata without relaunching or switching focus. See [ADR-0175](./adr/0175-mcp-vault-lifecycle-uses-app-registry.md).\n\nThe status-bar vault menu can open a registered vault as an independent desktop application instance. `VaultMenu` owns rendering, while `vaultMenuInteractions`, `vaultMenuActions`, and `vaultMenuTrigger` isolate mounted-workspace behavior, action construction, and trigger styling. Ordinary launches still register the single-instance plugin, while an explicit vault-instance launch bypasses it and scopes that process's active vault without changing the shared registry's ordinary startup selection. Packaged macOS builds use Launch Services `open -n` so each vault receives its own Dock tile and Command-Tab entry. `src/shared/workspaceColorContract.json` is the cross-runtime source for renderer picker keys, launch validation, persisted vault-color normalization, and light/dark native icon tints. Each process's AppKit icon is generated from the bundled Tolaria icon by recoloring its droplet with the launched or active vault's declared native tint; renderer-only `pink` and `gray` colors explicitly fall back to the contract's default blue tint. See [ADR-0171](./adr/0171-separate-vault-application-instances.md).\n\nVault item deep links use the registered vault list as their resolver namespace. `src/utils/deepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from workspace aliases, labels, and paths, appending a short stable hash when generated slugs would collide. `useDeepLinks` validates incoming links, broadcasts cross-window route requests so an already-open matching vault instance can claim and focus the link, then falls back to switching the receiving process's vault when no matching window answers. The owning window reloads the vault index once for recently changed files and opens the matching `VaultEntry` through the normal note-selection path.\n\nSaved Views participate in that mounted graph as source-scoped chrome. `useVaultLoader` loads view definitions from every mounted vault, annotates each `ViewFile` with its owning `rootPath` and workspace identity, and keeps sidebar selection/persistence keyed by `(rootPath, filename)` so same-named view files from different vaults stay independent.\n\nGit surfaces resolve repository paths explicitly. `useGitRepositories` derives the active repository set from the mounted available workspaces, keeps separate selected repositories for Changes, Pulse/history, and manual commits, and exposes the combined modified-file count for status/commands. Native Git capability checks treat a mounted workspace as Git-backed when it is inside any Git work tree, so included vault folders can reuse a parent repository without creating embedded `.git` directories. Rust resolves each vault to a `GitWorkspace` (`vault_root`, provider-discovered `git_root`, and repository-relative `vault_pathspec`). Vault-scoped status, diff, history, dates, pulse, conflict, and commit commands execute at the Git root with that pathspec, then map Git output back to vault-relative note paths. Branches, remotes, fetch, pull, push, merge, and rebase remain repository-wide; the settings row exposes the resolved root and parent-repository sync feedback identifies that wider scope. App-created commits use path-limited staging plus `git commit --only` so sibling staged and unstaged work is preserved. Mounted folders outside Git return no changes/no remote for aggregate probes instead of failing workspace-wide sync. AutoGit checkpoints iterate that repository set, while manual commit, history, diff, and discard operations use the selected surface or the note's workspace provenance. See [ADR-0161](./adr/0161-parent-git-workspace-context.md).\n\nGit command launch is also installation-local. The Rust Git module resolves a `GitLaunchConfig` from app settings before spawning Git, defaulting to native `git` while allowing Windows installations to explicitly select WSL2 Git and a distribution. WSL-selected launches go through `wsl.exe --exec git` and translate Windows/WSL UNC vault paths before passing repository paths across the command boundary; Tolaria never silently switches providers based on detection alone.\n\nRenderer git file workflows stay behind `useGitFileWorkflows`. The hook resolves per-note repository paths, queues editor diff requests, opens Pulse history entries including deleted-file previews, and keeps discard/reload handling close to the selected Git surface while `App.tsx` only wires the resulting callbacks into `NoteList`, `PulseView`, and `Editor`. `useInitialPulseLoad` separately owns cancellable Pulse initial, refresh-key, and retry requests; pagination remains in `PulseView`. Note history reloads only when its note, visibility, or explicit refresh generation changes; changing renderer callback identities during an editor-state update does not rerun `git log`, and the native history command executes that blocking process on Tokio's blocking pool.\n\nCross-workspace note reads and writes keep the disk-first invariant. When an absolute note path is saved or read without an explicit `vaultPath`, the Tauri boundary resolves the deepest registered vault root that contains the path and validates against that root before touching disk. This lets an editor tab opened from a mounted workspace save back to its source repository while preserving the same path-escape protections as active-vault operations.\n\n#### Note Opening Fast Path\n\nNote opening uses bounded in-memory fast paths for raw content and parsed editor blocks. `useTabManagement` owns the markdown/text prefetch cache and treats every cached value as a performance hint only: identity-matched entries (`modifiedAt` + `fileSize`) can be reused immediately, while identity-missing or identity-mismatched cached text is checked with `validate_note_content`, which compares the cached text with the current file bytes inside the validated vault boundary. If validation fails, Tolaria discards the cached entry and reads fresh disk content before swapping the editor.\n\nThe note list opportunistically preloads visible and adjacent markdown/text entries after a short delay. When a large warmed Markdown note resolves, `useEditorTabSwap` may parse it into a bounded parsed-block cache only after foreground editor work has been idle and the rich editor is mounted. On foreground open, block resolution checks the hot tab cache, exact-source parsed-block cache, blank/H1 fast path, and a direct Markdown-to-block parser for large common Markdown before falling back to BlockNote's parser. The direct parser runs in a module worker when available and falls back synchronously only for tests or runtimes without workers. It is conservative: unsupported Markdown constructs return to the BlockNote path rather than changing semantics. Large resolved block sets mount progressively: the editor is locked, the first chunk is applied immediately, remaining chunks append across animation frames, and the same generation/source-content token can abort the partial mount before it is committed. Progressive chunk boundaries expand through contiguous numbered-list items so BlockNote receives each ordered-list run in one mutation and preserves continuous numbering. Every programmatic document replacement, including progressive chunks and fallback recovery, is excluded from ProseMirror history so note navigation cannot retain previous document graphs or displace user typing from undo. Ordinary BlockNote block wrappers stay fully measurable after mount so hover side menus, slash dialogs, and document-end interactions do not inherit browser lazy-height scroll jumps. Parsed blocks are keyed by vault, path, and exact source content; every async swap carries a generation/source-content token so stale conversion results cannot overwrite newer file content or dirty editor state. The editor never renders a preview surface that later morphs into BlockNote. Development builds log `editorBlockResolve`, `editorBlockApply`, and `parsedBlockPreload` timings for large or slow note opens. See [ADR-0105](./adr/0105-editor-correctness-and-responsiveness-contract.md).\n\nRich-editor Markdown boundaries stay on the editor fast path while keeping one owner for import, installation, and export. `src/utils/richEditorMarkdown.ts` installs the direct serializer on real BlockNote editors, preprocesses Markdown before parsing, injects Tolaria schema blocks after parsing, and serializes body/document Markdown for saves, raw-mode entry, tab swaps, and position snapshots. Its lower-level direct serializer (`src/utils/blockNoteDirectMarkdown.ts`) serializes common Tolaria block shapes to Markdown with a per-editor weak cache keyed by stable block object identity and list context. Durable Markdown bridges for wikilinks, linked inline-code labels, math, highlights, files, Mermaid, tldraw, sandboxed HTML, blockquote callouts, and literal path-template backslashes stay behind `richEditorMarkdown.ts`; linked code and vulnerable literal backslashes are protected by Markdown-inert placeholders during import and restored before editing, while the direct serializer keeps safe identifier underscores, Windows-style separators, and line-ending-adjacent formatting delimiters stable. The bridge helpers preserve unchanged block objects so repeated debounced saves can reuse direct Markdown projections. Highlights use portable Bear-compatible prefixes for custom colors (`==🔴red==`, `==🔵blue==`, `==🟣purple==`, and `==🟢green==`) while the prefix-free `==text==` form remains the default yellow. Callouts keep marker metadata in block props but retain their body as editable inline content, and their mixed serializer restores portable `> [!type]` syntax. Stable active-tab rerenders trust the exact-source block cache before doing any full-document Markdown comparison, so unrelated React state changes do not serialize large notes. Unsupported block shapes fall back to BlockNote's lossy serializer, and development builds log `richEditorSerialize` timing/cache/fallback metrics plus rare `editorStabilityCheck` fallback comparisons for large or slow serializations.\n\nRich-editor typing keeps BlockNote's per-transaction work minimal. Tolaria creates BlockNote with animation bookkeeping disabled so the `previousBlockType` extension, which scans the old and new ProseMirror documents on every edit, is not installed. Collapsed-heading rendering attaches its mutation observer and `editor.onChange` subscription only while at least one heading/list section is collapsed, then detaches when the final section expands. Heading folds follow the document outline through divider blocks and stop at the next same-or-higher heading. Development builds wrap ProseMirror dispatch once per editor view and log `richEditorDispatch` timings for large or slow transactions, giving QA a direct signal for edit-time stutter instead of only note-open speed. The pinned `prosemirror-view` dependency normalizes a stale child boundary to the live parent end during DOM-selection synchronization so editor teardown or remount cannot dereference a child removed mid-scan; see [ADR-0183](./adr/0183-normalize-stale-prosemirror-selection-boundaries.md).\n\nCore perceived latency is protected by a synthetic production-browser benchmark. `pnpm perf` injects deterministic 500-entry startup/note fixtures and a 2,000-entry virtualized-list fixture, discards a warmup, then aggregates five measured runs. Startup phases publish standard `tolaria:<phase>` User Timing marks; note scenarios combine visible editor/content milestones with opt-in note/editor trace durations; list scrolling waits through the destination paint. `.editor-performance-thresholds.json` stores ratcheted median budgets and p90 diagnostics. CircleCI runs the gate in a dedicated browser job, while native `TOLARIA_STARTUP_TRACE=1` remains the richer host-level diagnostic tier. See `docs/PERFORMANCE.md` for the instrumentation audit and baseline policy.\n\n## Tech Stack\n\n| Layer | Technology | Version |\n|-------|-----------|---------|\n| Desktop shell | Tauri v2 | 2.10.0 |\n| Frontend | React + TypeScript | React 19, TS 5.9 |\n| Mobile shell | Expo React Native | Expo 54, React Native 0.81 |\n| Editor | BlockNote | 0.46.2 |\n| Editor render extensions | @tiptap/pm | ProseMirror decorations for rich-editor node presentation |\n| Code block highlighting | @blocknote/code-block | 0.46.2 |\n| Additional code grammars | @shikijs/langs | 3.23.0 |\n| Diagram rendering | Mermaid | 11.14.0 |\n| Whiteboard rendering | tldraw | 4.5.10 |\n| Raw editor | CodeMirror 6 + official language packages | Markdown, standalone/fenced HTML, YAML, JSON, Python, SQL, JS/TS |\n| Spreadsheet editor | IronCalc workbook + WASM | 0.5.x |\n| Styling | Tailwind CSS v4 + CSS variables | 4.1.18 |\n| UI primitives | Radix UI + shadcn/ui | - |\n| Mobile UI primitives | Nativewind + React Native Reusables via Tolaria-owned wrappers | Nativewind 4, RNR registry primitives |\n| Icons | Phosphor Icons | Full catalog; per-icon modules load on demand |\n| Build | Vite | 7.3.1 |\n| Backend language | Rust (edition 2021) | 1.77.2 |\n| Frontmatter parsing | gray_matter | 0.2 |\n| Filesystem watcher | notify | 6.1 |\n| AI (workspace) | CLI agent adapters (Claude Code + Codex + GitHub Copilot + OpenCode + Pi + Antigravity + Kiro + Hermes Agent) plus configured local/API model targets | - |\n| Search | Keyword (walkdir-based file scan) | - |\n| Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync |\n| MCP | @modelcontextprotocol/sdk | 1.0 |\n| Tests | Vitest (unit), Playwright (E2E/smoke), cargo test (Rust) | - |\n| Package manager | pnpm | - |\n\nThe icon registry derives its stable kebab-case catalog from the Phosphor CSR\nmodule filenames at build time. Each icon remains a separate Vite dynamic\nimport, so resolving an uncommon vault icon loads only that SVG component.\nThe type customization grid renders the catalog in progressive batches while\npreserving search across every registered icon. See [ADR-0174](./adr/0174-lazy-full-phosphor-icon-catalog.md).\n\n### Mobile App Foundation\n\nStandalone iOS editor text saves use the existing workspace module's asynchronous\natomic writer (ADR 0203). A private, versioned recovery record is published before\nreplacement; restart recovery precedes indexing, reads, and Git synchronization.\nUnexpected destination changes retain the pending draft and fail closed. This\nprotects published text saves, not unsent keystrokes. Structural entry operations\nuse the same recovery boundary: exclusive rename for moves, private quarantine\nbefore deletion cleanup, and UUID completion receipts for safe queue retries\n(ADR 0204). Text and entry retries consult completion receipts before capturing\nnew preconditions, so replay cannot overwrite later edits or delete recreated\nentries (ADR 0206). A move plus its subsequent backlink rewrites is not yet one transaction.\nExpo Go and Android still require their own equivalent persistence implementation.\n\nThe native text engine also supports a version-two journal containing an entire\ntext-only batch (ADR 0207). It preflights all prior digests and retains the plan\nwhen an external edit prevents replay. Single-save journals and receipts remain\nbackward readable. The repository routes entirely text-based multi-write plans\nthrough the native batch capability, coalescing repeated paths to their final\ncontent. Single saves, mixed structural plans, and runtimes without that\ncapability retain their existing individual-operation queue boundaries.\n\nThe same iOS module exposes an optional, single-call Git metadata reader anchored\nto the app sandbox. It returns fresh stat fields without caching file state;\nExpo continues to own byte IO. Rapid-edit staging and checkout recovery remain\nunchanged (ADR 0205).\n\nMobile saved-view loading uses `mobileViewYaml.ts` and the `yaml` parser with\nbounded input, alias expansion and filter depth (ADR 0210). Valid desktop YAML\nindentation and flow collections retain their filters; malformed definitions are\nrejected instead of becoming all-note views. Evaluation and serialization remain\nin `mobileSavedViews.ts`. Sidebar type discovery has no arbitrary count cap and\nuses the desktop pluralization helper, while explicit type labels take priority.\n\n`MobilePanelSafeArea` owns each tablet pane's top inset, background and divider.\nThere is no workspace footer: Settings lives at the sidebar bottom. The phone\nshell colors its top inset from the active panel. Panes and their dividers reach\nthe bottom edge; bottom controls and scrollable inspector content own their\nhome-indicator clearance without leaving a separate band below the workspace.\n`MobileGitHubRepositories` uses a bounded, searchable FlatList picker, and\n`MobileDeviceCode` copies authorization codes without routing secrets through the\ndiagnostic clipboard helper. Inspector relationship groups expose their own add\naction, and native option rows use explicit styles on the RNR button primitive.\n\nThe vault catalog remembers local imports alongside Git working copies (ADR\n0216). Each new local import has its own app-owned directory; the old single\nimport is registered in place once. One mutually exclusive active pointer is\nrestored before workspace loading. Forgetting changes only the catalog, never\nthe vault's files. Settings reuses the vault manager's GitHub account controller\nand hands off vault operations through the existing editor-save barrier.\n\nLazy note hydration reparses only the loaded target, resolves its relationships\nagainst the full note pool, and retains unrelated note object identities. It\ndoes not emit filesystem writes or change modified timestamps. The native\nread-only benchmark measures hydration separately from initial indexing and\ntype filtering; these are data-path timings, not screen-transition timings.\n\nStorage verification can also run against an optimized local binary using the\nexplicit `TOLARIA_QA` Swift condition (ADR 0208). A read-only native capability\nenables the existing loopback-only probes without Metro or development JavaScript.\nOrdinary Release excludes that native API. Git QA records JavaScript timer delay\nseparately from clone/push/pull duration; neither measure represents native frame\nrate or a physical-device performance budget.\n\nPhone layout uses `useMobileDeviceLayout` instead of an orientation-sensitive\nwindow-width breakpoint. Its actual panels occupy fixed slots on one retained\nrail; `usePhonePanelRail` drives that rail through RNGH and Reanimated on the UI\nthread. Incoming panes are not duplicate previews, and settling does not reset\nthe offset or start a second entrance animation. `usePhoneViewportWidth` accounts\nfor the outer safe area once, so landscape panes and edge gestures use the real\ncontent viewport. Inactive content is excluded from input and accessibility;\nthe editor is created on its first visit and retained thereafter. See ADR-0224\nand ADR-0244. Native acceptance covers cancellation, draft/focus continuity,\nscroll position, rotation, note-row actions and corresponding iPad behavior.\n\nThe mobile app lives in `apps/mobile` as a separate Expo React Native workspace package. It is intentionally not a port of the desktop React DOM component tree. Desktop components, CSS variables, and panel semantics are treated as the reference contract, while mobile uses native primitives and fixture-driven screens. Distribution builds use Expo SDK 57 with the supported `expo-build-properties` scene-lifecycle opt-in for Xcode 27/iOS 27 compatibility (ADR 0246). Prebuild generates Expo's scene manifest and window owner; the temporary Expo Go 54 preview constraint is superseded. Native dependencies and scene support require a new signed build and OTA fingerprint, not a JavaScript-only update.\n\nThe first mobile surface is a UI lab with real workspace semantics behind a fast fixture default. `apps/mobile/src/components/ui` owns RNR-derived shadcn-style primitives such as text, buttons, and badges, while `apps/mobile/src/ui` owns Tolaria-specific wrappers such as icon buttons, panels, toolbars, list rows, chips, and property rows. Workspace components live under `apps/mobile/src/components/workspace` and consume `MobileWorkspaceSnapshot` from `apps/mobile/src/workspace/mobileWorkspaceModel.ts`. Tablet panel gestures use one workspace-level horizontal responder and Reanimated offsets. The sidebar and note list remain mounted as one strip with desktop-token snap points at 0, -260, and -600 pixels, so a drag begun anywhere in workspace content can directly hide or reveal both panels without lifting. Only a hidden Properties panel reserves a 32-pixel right-edge reveal gesture; the editor toolbar exposes the equivalent explicit toggle. The native panel-gesture QA maps React Native accessibility frames onto the rotated Simulator window, injects sampled real drags from content and the screen edge, and verifies each settled panel state through stable test identifiers. The default repository remains fixture-backed for screenshot speed, while the Playwright QA harness can inject a real read-only local-vault snapshot through `tolaria:mobile-workspace-snapshot` when `MOBILE_QA_VAULT_PATH` is set. Explicit `source=native-vault` mode uses `expo-file-system` through `apps/mobile/src/workspace/expoWorkspaceFileSystem.ts`, reads an Expo-accessible vault directory, and persists reducer write plans back to markdown files and directories. On iOS, selecting a vault imports it atomically into the app-managed `Documents/Tolaria Vault` directory because document-picker access to arbitrary external folders cannot be reacquired after process termination; the custom `TolariaWorkspaceAccess` Expo module restores that managed local copy on cold launch and the app exposes Documents through Files. For launch performance, that module enumerates the managed vault off the JavaScript thread and returns one serialized file index to the existing pure snapshot builder, avoiding thousands of per-file React Native bridge calls; editor hydration and reducer write plans continue to use the filesystem repository rather than treating the launch index as a second persistence model. `apps/mobile/src/workspace/localVaultSnapshot.ts` keeps scanning as a pure files-plus-folder-paths transform: it parses Tolaria frontmatter conventions, preserves explicit empty folder paths for the sidebar, derives type colors from Type documents, caps rendered note rows for fast UI work, and is shared by host-vault QA and native-vault mode.\n\nTablet editing is reducer-owned and repository-persisted. `apps/mobile/src/workspace/mobileWorkspaceEditing.ts` applies note creation, title/body edits, frontmatter scalar property changes, relationship add/remove, archive, favorite, folder create/rename/delete, saved-view definition changes, primary note-list display-property config changes, and Type definition actions against the current `MobileWorkspaceSnapshot`. Note folder moves and explicit filename renames emit `moveNote` file-write plans plus only the `saveNote` writes needed for backlink rewrites, while folder edits update explicit folder paths, retarget notes in renamed subtrees, rewrite affected path-based wikilinks, and emit directory write plans instead of modeling folder renames as note delete/save loops. Saved-view edits persist desktop YAML fields including filters, `order`, and `listPropertiesDisplay`, so mobile view ordering and saved-view note-list column choices use the same file contract as desktop. Primary All Notes and Inbox display-property choices emit `saveVaultConfig` plans and are stored outside the vault file list by the repository adapter, matching the desktop vault-path-local config contract. Type document create/delete/update/reorder logic lives in `apps/mobile/src/workspace/mobileWorkspaceTypeEditing.ts` and persists the Type markdown document frontmatter for sidebar label, visibility, color, sort, note-list display properties, and instance schema/default fields through `apps/mobile/src/workspace/mobileTypeDefinitions.ts`; `apps/mobile/src/workspace/mobileTypeDefinitionSchema.ts` owns the form conversion between custom scalar properties, wikilink relationship fields, and blank built-in relationship placeholders. `apps/mobile/src/screens/tabletWorkspaceHistory.ts` records the mobile command history at the snapshot boundary: content/frontmatter edits replay as content writes, structural note, folder, saved-view, and Type-definition changes replay through exact restore edits, and `apps/mobile/src/screens/tabletWorkspacePathHistory.ts` routes note moves, note filename renames, and folder subtree renames back through inverse reducer commands so undo/redo preserves filenames, ordering, raw Markdown, backlink rewrites, and persisted write paths. `apps/mobile/src/workspace/mobileNeighborhood.ts` mirrors the desktop Neighborhood note-list contract for mobile snapshots by pinning the source note, grouping Type instances, direct relationships, inverse relationships, and Backlinks with the same desktop group labels, and preserving duplicate notes across groups. The reducer preserves frontmatter while the markdown editor edits body content, derives snippets/blocks/link targets back from Markdown, resolves wikilink relationship targets against the snapshot, and returns write plans. The source editor and web WYSIWYG fallback keep keystrokes in a local draft and flush through that reducer after a short idle window or when editing unmounts, matching the desktop edit-buffer boundary instead of rebuilding the workspace graph for every character. Mobile note snapshots preserve frontmatter aliases so wikilink resolution and editor autocomplete can match desktop identity candidates. The repository boundary decides whether those writes are no-ops for fixtures, host-injected QA logs, installation-local config writes, or native Expo filesystem writes. Visual components do not import filesystem APIs directly.\n\nThe native mobile WYSIWYG editor is wrapped behind `MobileWysiwygMarkdownEditor`. It registers Tolaria-owned TenTap bridge extensions only at that boundary and passes a generated Tolaria TenTap WebView source through `customSource`, so custom Tiptap nodes exist inside the editor WebView instead of only in React Native. `MobileWysiwygContentBridge.ts` hydrates programmatic document content without emitting user-edit updates, and the React Native lifecycle keeps persistence gated until an explicit touch reaches the editor surface; lazy note hydration therefore cannot be mistaken for a user edit, while the first intentional keystroke still saves normally. `MobileWysiwygMathBridge.ts` adds the `mathInline` and `mathBlock` nodes required for desktop math parity and renders both through the mobile KaTeX MathML helper. `MobileWysiwygTentapWebEditor.tsx` plus `apps/mobile/scripts/build-wysiwyg-editor-html.mjs` own the rebuildable WebView bundle. `mobileInlineMath.ts`, `mobileDisplayMath.ts`, `mobileDocumentContent.ts`, and `mobileWysiwygInputTransforms.ts` keep parsing, hydration, structural input transforms, and Markdown serialization pure and testable. Native WYSIWYG QA probes live beside the mobile editor boundary and are opened through Expo deep links; external links now have a dedicated simulator proof that applies and removes a TenTap link command, then verifies the serialized Markdown output rather than relying only on model tests.\n\nMobile slash commands and inline Find/Replace use that same TenTap boundary. Their ProseMirror plugins own query matching, decorations, keyboard selection and undoable document changes inside the WebView; narrow bridge messages expose state and actions to native RNR controls. Find uses the desktop's pure matching and safe-regex helpers without importing desktop UI. The mounted editor registers Find/Replace with the existing editor-command registry, so keyboard shortcuts and overflow actions reach the inline bar instead of a workspace modal. Source-mode Find uses the same bar and overlays its active match in the existing Markdown syntax layer, retaining YAML frontmatter. Tests mount the actual TenTap web runtime to avoid mixing incompatible ProseMirror instances, and native XCTest checks touch targets, inline placement and serialized Markdown.\n\nMobile note deep-link copy mirrors the desktop URL contract without importing desktop React DOM modules. `apps/mobile/src/workspace/mobileDeepLinks.ts` builds `tolaria://<vault-slug>/<relative-path-with-extension>` URLs from the mobile snapshot source and selected note path, while `apps/mobile/src/workspace/mobileClipboard.ts` writes through Expo Clipboard. The action is exposed through the tablet controller/action-sheet callback boundary so visual rows remain platform-agnostic.\n\nMobile note PDF export follows that same boundary. `apps/mobile/src/workspace/mobilePdfExport.ts` strips YAML frontmatter through the mobile document-content helper, renders the note body to Tolaria-styled HTML, and uses `expo-print` plus `expo-sharing` only when running in the native Expo app. Browser-based UI lab checks record deterministic export attempts instead of opening a print dialog, and the generated PDF is treated as a temporary share artifact rather than a new vault file.\n\nMobile attachment import and opening also follow the controller/editor callback boundary. `apps/mobile/src/workspace/mobileAttachmentImport.native.ts` uses `expo-document-picker` plus the Expo FileSystem object API to copy selected files into the active vault's `attachments/` directory, while `mobileAttachments.ts` owns safe filenames and portable Markdown image/link serialization. `apps/mobile/src/workspace/mobileAttachmentUris.ts` is the native-boundary adapter that resolves portable `attachments/...` Markdown targets to Expo file URIs for TenTap/link opening, then converts vault-local file URIs back to portable Markdown before save. Source and WYSIWYG editor surfaces receive importer/open-link callbacks and attachment payloads instead of importing native picker, sharing, or filesystem modules directly.\n\nThe mobile command palette mirrors the desktop command boundary without importing desktop React hooks. `apps/mobile/src/workspace/mobileCommandPalette.ts` builds enabled mobile command rows from existing workspace callbacks, preserving shared IDs from `src/shared/appCommandManifest.json` when a command is manifest-backed and using desktop dynamic command-palette IDs for rows such as note retargeting, file utility actions, and note-width changes. Selection-sensitive editor actions such as paste-without-formatting are registered by the mounted source or native WYSIWYG editor through `apps/mobile/src/workspace/mobileEditorCommands.ts`, so tablet and phone palettes can dispatch the active editor command without importing editor internals. `apps/mobile/src/components/workspace/MobileCommandPalette.tsx` owns only the native searchable/grouped surface; create/search/navigation/note/file/view/settings execution remains in the reducer/controller callbacks. Hardware keyboard commands are exposed through the tracked `TolariaKeyCommands` Expo module: its React delegate handler supplies the root UIKit controller and registers priority `UIKeyCommand`s, then forwards normalized events to the same JavaScript router used by DOM keyboard QA. The root controller accepts first-responder status when no text input owns focus. JavaScript retains one subscription across renders and reads current handlers through a ref. Cmd+1/2/3 and Cmd+Shift+I use the existing tablet panel controller. Unmodified arrows belong to the focused native text input, the open command palette, or the visible note list, in that order; modal/hidden lists cannot consume them. The palette explicitly registers its lifetime with the native module, receives arrows/Escape there, and uses the input's submit event for Return. Native XCTest exercises modifier shortcuts across one workspace session; `assert-ios-keyboard-navigation.mjs` uses AXe HID events and actual accessibility results for arrows/Return/Escape, which XCTest on iOS 18.3 cannot reliably synthesize. Neither harness treats bridge-event logs alone as a successful interaction.\n\nThe mobile launcher name is `Tolaria`; the existing package and bundle identifiers\nremain unchanged so native updates retain their application data.\nThe Expo launcher icon uses opaque 1024px light/dark variants in\n`apps/mobile/assets/icon-light.png` and `icon-dark.png`. They preserve the desktop\ndroplet paths and gradients from `src-tauri/icons/icon-source-dark.svg`, replacing\nonly its macOS rounded tile, shadow and transparent outer margin with full-bleed\nlight/dark backgrounds. iOS owns the launcher mask, avoiding an inset white tile.\nExpo supplies the native sizes and appearance catalog. Desktop assets remain\nunchanged. Rebuild the native app after changing these assets or configuration.\n\nThe Expo dynamic configuration keeps development on `com.tolaria.mob\n\n<!-- File: CLAUDE.md (Tokens: ~1184 | Category: claude-rule) -->\n---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Claude Code compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n\n<!-- verity-memory:start -->\n## Project Memory\n\nThis project has a knowledge graph maintained at `.verity/memory/`. Before starting\nnon-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns\nthat may apply to the change you are about to make. Open specific node files via\nthe Read tool when the title or scope suggests relevance.\n\nThe graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are\nsuperseded — ignore them unless investigating history.\n\nKnowledge the organization's other repositories learned is mirrored outside the\nrepo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).\nEach claim says where it came from; one that is wrong here is demoted for\neveryone with `verity memory demote <id> --reason \"…\"`.\n\n## Quality gate: accepted risks\n\nWhen the Verity pre-commit/pre-push gate FAILs, fix the findings — that is the\ndefault. Use `verity waive <pattern-id> --file <path> --reason \"…\"` ONLY to relay\na risk a human has explicitly accepted: a named code-review finding, an ADR, or\nthe user saying so in this conversation. The --reason must cite that source.\n\nNever waive on your own judgment, to get past a block, or pre-emptively. A waive\nbinds to the file's current bytes and voids automatically when the file changes,\nand every waive is recorded in the run ledger. For a pattern-level false positive\nuse `verity feedback finding <run-id> <pattern-id> false_positive` instead.\n\n## Post-task reflection\n\nWhen a task is complete (you've created a PR, the user says \"done\" or \"ship it\",\nor the work is clearly finished), **draft the reflection yourself** — 1–3\nconcrete things worth remembering (a decision, a gotcha, or a pattern), each\ncited with the files / PR / commands / error-signatures it came from. Skip\nentirely if nothing non-obvious happened — that judgement is the ONLY filter,\nbecause nothing reviews the reflection before it lands.\n\nThen record it straight away. There is no confirm step, in any environment:\n\n```bash\nverity reflect --user-input - --kind <kind> <<'VERITY_EOF_<random>'\n<your draft>\nVERITY_EOF_<random>\n```\n\nThe draft goes through the quoted heredoc, never inside `\"…\"` on the command\nline, where a `\"` or `$(…)` in it would run in the shell. Replace `<random>`\nwith 8 random letters and digits that you pick for this call, the same at both\nends. Text written before you picked them cannot end the heredoc early.\n\nAdd `--confirmed` ONLY when the user authored or dictated the words. Without\nit the node is stored as `source: agent` — Verity thought this, nobody checked\nit. With it, `source: user` at full confidence — a person stands behind this.\nNever claim the second for your own draft, however good it is.\n\n**Name the files in the text.** Verity scopes the reflection to the paths it\ncites, and a reflection that names no file in this repo is never retrieved for\na later review — it is recorded and then invisible. The command says so when it\nhappens; `--file-globs '<path or glob>'` is the fix when the prose cannot carry\nthe paths.\n\nThen tell the user, in one line, what you recorded and where: the command\nprints the node id, the path under `.verity/memory/`, and a dashboard link.\nThey did not agree to it in advance, so say it happened — editing or deleting\nthat file is how they correct it.\n\n> Durable, hand-curated guidance goes in the preserve region below (it survives\n> regeneration) or anywhere OUTSIDE these markers. Everything else between the\n> markers is tool-owned and overwritten on each run.\n\n## Housekeeping Turns\n\nWhen a turn will be pure housekeeping — pulling, installing dependencies,\nrebasing, a formatting sweep you are not authoring — declare it BEFORE doing it:\n\n```bash\nverity ignore --turn --agent --reason \"pulling latest before starting\"\n```\n\nThis skips the review for that turn, which saves the turn Verity would\notherwise spend saying it had nothing to say. Use `--for 30m` instead of\n`--turn` when a single piece of housekeeping spans several turns.\n\n**It is a claim about the turn, not a way to silence review.** The declaration\nis checked against what the turn actually did: if anything is authored — by you,\nby a subagent, or by a shell command that can write files — it voids, the review\nruns anyway, and the broken declaration is reported. So declare housekeeping you\nare about to do, never work you have already done, and never as a way to get past\na finding. Declarations are budgeted per session and every one is recorded with\nits reason.\n\n<!-- verity-memory:preserve -->\n<!-- Add binding, hand-curated guidance here; it survives Verity regeneration. -->\nVerity is set up only on some machines (currently the agent-claude box). If the `verity` CLI or\n`.verity/memory/` is missing, skip the Verity steps above; `AGENTS.md` stays the source of truth.\n<!-- /verity-memory:preserve -->\n<!-- verity-memory:end -->\n\n<!-- File: GEMINI.md (Tokens: ~36 | Category: gemini-rule) -->\n---\ntype: Note\n_organized: true\n---\n\n@AGENTS.md\n\nThis file is only a Gemini CLI compatibility shim. Keep shared agent instructions in `AGENTS.md`.\n\n<!-- File: .claude/commands/create-adr.md (Tokens: ~935 | Category: command-prompt) -->\n# Create Architecture Decision Record\n\nUse this command when you need to document an architectural decision made during a task.\n\nInspired by [adr-tools](https://github.com/npryce/adr-tools) (Nygard format), adapted for Laputa's frontmatter-based note format.\n\n## When to use this\n\nCreate an ADR when your work involves any of these:\n- Choosing a storage strategy (vault vs app settings vs database)\n- Adding or removing a major dependency\n- Supporting a new platform or target\n- Introducing or removing a core abstraction\n- Making a cross-cutting decision that affects how future code should be written\n\nDo NOT create ADRs for: bug fixes, UI styling, refactors that preserve behavior, or test additions.\n\n## Creating a new ADR\n\n### 1. Find the next ID\n\n```bash\nls docs/adr/*.md | grep -oP '\\d{4}' | sort -n | tail -1 | xargs -I{} printf '%04d\\n' $(({} + 1))\n```\n\nIf no files exist yet, start at `0001`.\n\n### 2. Create the file\n\nFilename: `docs/adr/NNNN-short-kebab-title.md`\n\nTemplate:\n\n```markdown\n---\ntype: ADR\nid: \"NNNN\"\ntitle: \"Short decision title\"\nstatus: active\ndate: YYYY-MM-DD\n---\n\n## Context\n\nThe issue motivating this decision, and any context that influences or constrains it.\n\n## Decision\n\n**The change we're proposing or have agreed to implement.** State it clearly in one or two sentences — bold so it stands out.\n\n## Options considered\n\n- **Option A** (chosen): brief description — pros / cons\n- **Option B**: brief description — pros / cons\n- **Option C**: brief description — pros / cons\n\n## Consequences\n\nWhat becomes easier or harder as a result?\nWhat risks does this introduce that will need to be mitigated?\nWhat would trigger re-evaluation of this decision?\n\n## Advice\n\n*(optional)* Input received before making this decision — who was consulted, what they said.\nOmit this section if the decision was made without external input.\n```\n\n### 3. Update the index\n\nAdd a row to `docs/adr/README.md`:\n\n```markdown\n| [NNNN](NNNN-short-kebab-title.md) | Title | active |\n```\n\n### 4. Include in the same commit as the feature\n\n```bash\ngit add docs/adr/NNNN-*.md docs/adr/README.md\n# fold into the feature commit — do not create a separate commit just for the ADR\n```\n\n---\n\n## Superseding an existing ADR\n\nEquivalent of `adr new -s <N>` from adr-tools — do this in two steps:\n\n### Step 1: Mark the old ADR as superseded\n\nEdit the existing file — add `superseded_by` and update `status`:\n\n```yaml\n---\ntype: ADR\nid: \"000N\"\ntitle: \"Old decision title\"\nstatus: superseded          # ← change from active\nsuperseded_by: \"NNNN\"       # ← add this\ndate: YYYY-MM-DD\n---\n```\n\n**Never edit the content sections** of an active ADR — only the status metadata.\n\n### Step 2: Create the new ADR\n\nFollow the steps above. In the **Context** section, reference the superseded ADR:\n\n```markdown\n## Context\n\nSupersedes [ADR-000N](000N-old-title.md).\n\n[explain why the old decision no longer holds]\n```\n\n### Step 3: Update the README index\n\nChange the old row's status to `superseded`, add the new row.\n\n---\n\n## Best practices (from adr-tools / Nygard)\n\n- **One decision per ADR** — if you find yourself writing \"and also\", split it\n- **Write Decision first** — if you can't state it in 1-2 sentences, the decision is too vague\n- **Context is the \"why now\"** — what forced this decision to be made today?\n- **Consequences should include negatives** — a one-sided ADR is a red flag\n- **Committed = immutable** — once pushed, the content doesn't change; only status metadata does\n- **If in doubt, create one** — cheaper to have an unnecessary ADR than to lose context\n- Date = today's date, `YYYY-MM-DD`\n\n<!-- File: .claude/commands/laputa-done.md (Tokens: ~459 | Category: command-prompt) -->\n# /laputa-done <task_id>\n\nMark a Laputa task as done: add completion comment, move to In Review, then self-dispatch the next task.\n\nRun this after Phase 1 (Playwright) and Phase 2 (native app QA) both pass **and `git push origin main` has succeeded**.\n\n⚠️ A task is NOT done until the push succeeds. If the push is blocked by the pre-push hook (clippy, tests, CodeScene, build):\n- Read the error\n- Fix it (never use `--no-verify`)\n- Commit the fix and push again\n- Repeat until push exits with code 0\n\n## Steps\n\n**1. Add completion comment to the task**\n\nSummarize what was done — this is the context Luca and Brian will read in Todoist:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"task_id\": \"$ARGUMENTS\",\n    \"content\": \"✅ Implementation complete.\\n\\n**What changed:** [brief summary of the implementation]\\n**ADR:** [if an ADR was created, reference it here; otherwise omit]\\n**Playwright:** all tests pass\\n**Native QA:** tested with pnpm tauri dev — [describe what was tested and what was observed]\"\n  }'\n```\n\n**2. Move task to In Review**\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/$ARGUMENTS/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjX33FF4Vj86M\"}'\n```\n\n**3. Pick the next task**\n\nRun `/laputa-next-task` to get the next task and start working on it immediately.\n\nIf there are no tasks, `/laputa-next-task` will wait 10 minutes and retry automatically. Do NOT exit — stay alive and let it loop.\n\n<!-- File: .claude/commands/laputa-next-task.md (Tokens: ~664 | Category: command-prompt) -->\n# /laputa-next-task\n\nPick the next Laputa task from Todoist and move it to In Progress.\n\nPriority order: **To Rework** first, then **Open** (sorted by Todoist priority p1→p4).\n\n## Steps\n\n1. Fetch tasks from To Rework (`6g6QqvR9rRpvJWvv`), then Open (`6g3XjWR832hVHhCM`)\n2. **Sort by priority — this is mandatory.** Todoist returns tasks in arbitrary order. You must sort them yourself:\n   - Todoist priority field: `4` = p1 (urgent), `3` = p2, `2` = p3, `1` = p4\n   - Sort descending by `priority` field (4 first, 1 last)\n   - To Rework tasks always come before Open tasks regardless of priority\n   - **Never pick a p3/p4 task if a p1/p2 task exists in the same section**\n3. Take the first task from the sorted list\n4. Move it to In Progress (`6g3XjWjfmJFcGgHM`) via Todoist API:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/tasks/<task_id>/move\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"section_id\": \"6g3XjWjfmJFcGgHM\"}'\n```\n\n5. Add a \"started\" comment to the task:\n\n```bash\ncurl -s -X POST \"https://api.todoist.com/api/v1/comments\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"task_id\": \"<task_id>\", \"content\": \"🚀 Starting work. [Brief description of approach or what needs to be fixed]\"}'\n```\n\n6. Fetch the full task details (description, comments) from Todoist:\n\n```bash\ncurl -s \"https://api.todoist.com/api/v1/tasks/<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n\ncurl -s \"https://api.todoist.com/api/v1/comments?task_id=<task_id>\" \\\n  -H \"Authorization: Bearer $TODOIST_API_KEY\"\n```\n\n6. For To Rework tasks: read the ❌ QA failed comment — it tells you exactly what to fix\n7. Output: task ID, title, and full description so you can start working immediately\n\nIf no tasks are available in either section → wait 10 minutes and try again (loop forever):\n\n```bash\nwhile true; do\n  # ... check tasks ...\n  if no_tasks; then\n    sleep 600  # 10 minutes\n  else\n    break  # got a task, proceed\n  fi\ndone\n```\n\nDo NOT exit when there are no tasks. Keep looping until a task appears. This keeps Claude Code alive permanently — the watchdog is a safety net only, not the primary dispatcher.\n</yakaai_skills>"}