---
title: KMS (the Vault) — notes, editor, search and sharing
---

# KMS (Markdown vault editor inside Omniscio)

## What it is

> **Status: Phases 1-3, 5, 6 shipped — full Markdown vault editor with images, AI summaries, and read-only agent tools.** Omniscio hosts a TipTap-based Markdown editor inside the KMS panel, with autosave, persisted open tabs, note CRUD (create / rename / move / soft-delete / hide), wiki-link back-reference rewriting on rename, wiki-link autocomplete, find/replace, source view toggle, a unified search modal across note titles + content + images, full image support (paste / drop / picker import, content-addressed asset dedup, gallery panel, full-screen lightbox, AI-derived per-image title / description / OCR / tags / category, image FTS5 search), per-note AI summaries with drift detection + opt-in auto-regen worker + in-editor status chip, and read-only MCP tools (including hybrid semantic search) that expose the vault to spawned Claude sessions. Writes land on disk under the configured vault root only; soft-delete moves notes into `<vaultRoot>/.trash/<timestamp>__<basename>` instead of unlinking. Image assets live in `<vaultRoot>/assets/<sha256>.<ext>` (content-addressed → automatic dedup across the vault). Phase 4 (Tasks UI), Phases 7 (Quick Compose + hotkeys), and Phase 8 (export, templates, crash recovery, etc.) are deferred or cut from scope (the KMS appearance / custom theme options from Phase 8 DID ship — see "Custom theme options") — see "What's still deferred". For a verified feature-by-feature parity map against the standalone "Knowledge Management Tool" (Nothari) app — what's present / partial / missing and a proposed build-wave roadmap to reach full parity — see [kms-parity-audit.md](../kms-parity-audit.md).

**KMS** is an integration that exposes a folder of Markdown notes (a "vault") to Omniscio as a knowledge surface you can both read and edit in place. You point Omniscio at a vault root on disk, and Omniscio parses every `.md` file under it once, then watches the folder for changes. The notes show up as a new "KMS" virtual project in the Omniscio sidebar group. From there you can browse the vault list, open a note in the editor, edit it (with autosave to disk), search across every note's body with FTS5, rename / move / soft-delete notes, and see external filesystem changes land in real time when something else on disk updates a file.

The vault format is plain Markdown — one note per `.md` file, YAML frontmatter optional, `#hashtag` and `[[wiki-link]]` syntax recognized but not required. The integration deliberately does not own the file format — it reads and writes whatever round-trips through the editor's serializer, and stays out of the way otherwise. You can continue using the KMS desktop app (or any Markdown editor — VS Code, Obsidian, plain Notepad) alongside Omniscio; the watcher picks external edits up and refreshes the sidebar.

**Off by default.** The integration is gated by the `nothariEnabled` setting (default `false`). With it off, no parser runs, no watcher runs, no DB tables are touched, the editor chunk never loads (TipTap is `React.lazy`-imported behind the panel), and the sidebar entry is hidden. The intended onboarding is: Settings → Features → flip **Enable KMS**, then Settings → KMS → set the **Vault root folder** to the absolute path of your vault (e.g. `C:/My Knowledge Vault/`). Once both are set, the indexer runs a full pass and the sidebar entry appears.

A one-time reset on **2026-05-26** (`src/main/services/force-off-features-migration.ts`) flipped `nothariEnabled` — along with Marketplace, Marketplace Review, and Tasks Outliner — OFF for **every** install, overriding any deliberate enable, so even users who had KMS running before now start with it off. After that one-time reset your toggle choice is authoritative: enable it once and it stays on across launches. The flag is read with `settings.nothariEnabled ?? false`, so an install that never persisted the field simply reads as off.

## Where to find it

The **KMS** panel in Omniscio's sidebar — that is the vault, and the editor inside it is where you write. The switches that control it live in **Settings**, where KMS itself has to be enabled before anything else here appears.

## How it behaves

### How to use it (Phase 2)

1. **Turn it on.** Settings → Features → flip **Enable KMS**.
2. **Point Omniscio at your vault.** Settings → Features → **KMS vault** → **Choose folder…** (desktop only) picks the vault root. The folder is validated first — it must be a real directory, not a system folder, a shortcut/symlink, or missing — and is saved ONLY if it passes, so a bad pick shows a plain-English reason inline instead of silently failing to index. A good pick applies immediately (no restart): the indexer kicks off a full scan and reports progress in the sidebar. Picking a **different** folder switches the open vault to it — the KMS panel follows the folder you've configured, not whichever vault you set first. (Changing the folder registers a NEW `nothari_vaults` row; the registry is ordered `created_at ASC`, so the active vault is resolved by matching the configured root in the main process — `getConfiguredVaultId()` → `KMS_VAULT_LIST.activeVaultId`, plus a `KMS_ACTIVE_VAULT_CHANGED` push for the live switch — never blindly `vaults[0]`. Pinned by I7 of the contract below.) The currently-set folder is re-checked whenever Settings opens, so a moved/deleted vault surfaces a ⚠ warning. The validate-before-persist seam reuses the same hardened `validateVaultRoot` the indexer uses, via the read-only `KMS_VALIDATE_VAULT_ROOT` IPC; pinned by the [KMS vault-root picker contract](/.claude/memory/contracts/kms-vault-root-picker-contract.md).
3. **Wait for the first index.** The first scan walks every `.md` file under the vault root, parses frontmatter / hashtags / wiki-links, and writes a row per note into Omniscio's SQLite DB. Subsequent launches are incremental — only changed files reparse.
4. **Open the KMS sidebar entry.** It appears in the Omniscio group. Clicking it mounts the KMS editor shell. The file tree lives in Omniscio's native Pane 2 sub-sidebar — the same slot Tools, Skills, Alarms, AI Coaching use — so you get the standard Omniscio sidebar affordances (resize handle, mobile drill-down). The center pane holds the open-tabs strip on top, the TipTap editor in the middle, and a thin status bar at the bottom. A side-panel drawer (Outline / Backlinks / Smart Connections / Tags / Bookmarks / Hidden) is available behind the toolbar's **Toggle side panel** button — closed by default after the 2026-05-21 layout rework. Open it on demand to inspect the active note's structure; click the X in its header to dismiss without leaving the editor.
5. **Open a note.** Single-click any row in the file tree. Each file row is labelled by the note's name with the `.md` extension hidden (the tree reuses the tabs' `deriveTitle` helper, so a note reads identically in the tree and its tab; folders show verbatim). This is display-only — the inline rename input still seeds with the full `.md` filename, so renames preserve the extension on disk. The note loads into the editor and a tab appears in the tabs strip. Open multiple notes — each gets its own tab, and the strip is preserved across relaunches via the `nothari_tabs` table. **Switching between open notes is instant**: the TipTap editor instance is reused across note switches rather than torn down and rebuilt (KMS nav speed Fix 4 dropped the per-tab `key` remount), so there's no rebuild flash. A cross-note switch still resets per-note state — empty undo history (Ctrl+Z can't bleed into the previous note) and scroll-to-top — exactly as the old remount did; a same-note external refresh preserves scroll + cursor. (Behavior pinned in the [KMS editor scroll-stability contract](/.claude/memory/contracts/kms-editor-scroll-stability-contract.md).)
6. **Edit.** Type into the editor. Five hundred milliseconds after you stop typing, the autosave debounce flushes — the body round-trips through the editor's markdown serializer, the frontmatter is re-attached byte-for-byte, and the file is written to disk. The write is atomic (temp file + rename); the rename is retried through a transient Windows file lock — a real-time AV scan, a file-sync client (OneDrive/Dropbox), or the Search indexer briefly holding the just-written temp file makes a bare `rename` throw `EPERM`, which used to lose the whole autosave — via the shared [`renameWithRetry`](/src/main/utils/atomic-file.ts) (`EPERM`/`EBUSY`/`EACCES`, bounded), so a momentary lock no longer fails a save. Autosave runs silently: there is no on-screen save/dirty indicator (the status bar's amber **Unsaved** chip and the tab's dirty dot were removed 2026-06-09 — the sub-second type→save→clear cycle made them flicker on every keystroke), and only a _persistently_ failed write (the retry exhausted, or a non-transient error) surfaces, as a toast. The internal dirty bit still exists and remains load-bearing (flush-on-close, flush-on-tab-switch, the privacy dirty-gate) — only its visual presentation was dropped.
7. **Find / replace (3-scope bar).** `Ctrl+F` opens the find bar; `Ctrl+H` opens it with the replace input visible. The bar carries **three scope tabs — This Note · All Documents · Images —** over one shared query box; `Tab` / `Shift+Tab` cycle them (or click a tab), and each tab shows a live result count. **This Note** searches the active note in **both** the rich editor and source view (source view uses textarea selection on the active match), with a `<current> / <total>` counter, `Enter` / `Shift+Enter` to step forward / back, case / whole-word / regex toggles, and replace / replace-all. **All Documents** + **Images** run the SAME vault-wide FTS as `Ctrl+P` (the `useUnifiedSearch` engine is shared, never duplicated) — arrow keys move the highlight, `Enter` opens: a document result switches back to the This-Note tab and opens that note with the query still highlighted, an image result opens the lightbox. `Esc` closes. This brings the Ctrl+F bar to parity with the standalone Nothari app's find bar; behavior pinned in the [KMS find/replace contract](/.claude/memory/contracts/kms-find-replace-contract.md).
8. **Source view.** `Shift+Ctrl+M` toggles the editor between the rich-text TipTap surface and a raw markdown textarea — useful for hand-editing tricky YAML frontmatter or pasting in pre-formatted content.
9. **Rename and delete.** The file tree's context menu (right-click a row) exposes **Rename** and — on file rows only — **Delete** (folders carry no Delete: KMS has no folder entity, so deleting a folder path would orphan its notes). Rename rewrites every `[[wiki-link]]` back-reference across the whole vault in a single transaction so links never dangle. Delete pops a danger confirmation ("moved to the vault trash … recover from the vault's `.trash` folder"), then soft-deletes by `relativePath`: the file moves into `<vaultRoot>/.trash/<timestamp>__<basename>` and the row is removed from `nothari_notes` (**recoverable by hand only until the retention sweep reaps it** — the periodic retention tick permanently deletes trashed note bodies older than your **Settings → General → Data Retention (days)** window, **30 days** by default; see [KMS part 6 → Privacy + safety contract](kms-part-6.md), and copy anything you want to keep out of `.trash` before then). Deleting an **open** note closes its tab and **discards** any in-flight autosave so no doomed write fires against the gone file. Select several rows (Ctrl/Shift-click) and the menu collapses to **Delete N items** — one confirmation, deleted deepest-first, partial failures tolerated. The Command-Palette "Delete note" does the same to the active note. (Move is drag-and-drop in the tree, not a menu item.) Behavior pinned in the [KMS delete contract](/.claude/memory/contracts/kms-delete-contract.md). The same menu's **Copy link** action (file rows only, right under Open) copies a deep link to the page — see [Deep linking to a note](#deep-linking-to-a-note).
10. **Hide a note (Phase 3 Sub-PR 3f-2).** The file tree's context menu also exposes **Hide** between Rename and Delete — file rows only, never folders. Hiding a note stamps a `hidden_at` timestamp on the row but leaves the file on disk untouched. Hidden notes are filtered out of the file tree, the search index, and link suggestions. They remain reachable through the right-pane **Hidden panel** (see below).
11. **Watch for external changes.** The file watcher debounces filesystem events and re-parses changed notes on the fly. The renderer receives a `kms:note-changed` push event with `{ vaultId, noteId, relativePath, op: 'added' | 'updated' | 'removed' }` and refreshes the tree + the editor if the changed file is the active tab. The watcher can be turned off at Settings → Features → KMS vault → **Watch vault for changes** (`nothariFileWatcherEnabled`, default on) — with it off, the vault is indexed once at startup and not re-scanned until the next launch.

The watcher's ignore list is hard-coded and MUST mirror the indexer's `IGNORED_DIRS` so the live watcher and the full re-scan agree on what is not a note: `.git/`, `.kms/`, `assets/`, `node_modules/`, and `.trash/` (so a soft-deleted note — which the CRUD service moves into `.trash/` — is not re-indexed and resurrected in search), plus any non-`.md` file. Internally the watcher does NOT use chokidar (which opens one OS file-watch handle per file — ~256 for a 243-note vault, tripping the resource-pressure alert): it puts a non-recursive `fs.watch` on each folder as a change signal, then `readdir`-diffs the folder to derive the real add/change/remove — so handle count scales with folder count, and it never trusts libuv's Windows directory-watch filename (which is corrupted). The vault root MUST be a real directory (no symlinks, no system paths, no traversal); every write-side handler funnels its user-supplied path through `resolveInsideVault()` which rejects anything outside the configured root.

### Only keep pinned notes as tabs (preview-tab mode)

By default every note you open adds a tab, and the strip caps at 10 **unpinned** tabs — open an 11th and the least-recently-active unpinned one drops off (pinned tabs never count against the cap). Turning on **Settings → Features → KMS → "Only keep pinned notes as tabs"** (`nothariTabsPreviewMode`, default off) switches to a single-preview model: opening a note reuses one ephemeral slot instead of appending, so the strip only ever holds your **pinned tabs + the note you're reading**. Open another note and it takes over that same slot. **Pin** a note to keep it. On desktop (with a mouse or trackpad) an unpinned tab shows **no** pin button — **right-click the tab → Pin**; a pinned tab then shows a small pin glyph you can click to unpin, so you can still see which tabs are pinned at a glance. On touch (a phone or tablet) the inline pin glyph is always shown on every tab — it is the only pin affordance there, since touch has no right-click. Either way, a pinned note becomes a permanent tab. Reloading the vault (or relaunching) collapses a strip that had accumulated unpinned tabs down to pinned + one preview, so the bar starts clean; the surviving preview is the one you were most recently on. Replacing the current preview **flushes its autosave first**, so an in-progress edit is never dropped. Off is byte-identical to today's append-and-cap behavior — nothing changes until you opt in. Pinned by the [KMS tab preview-mode contract](/.claude/memory/contracts/kms-tab-preview-mode-contract.md).

### Collapsing the file-tree sidebar

The KMS file-tree sub-sidebar (Omniscio's native Pane 2 — the notes/folder list) can be **collapsed** the same way the regular Sessions and Projects sidebars hide. A **collapse button** (a chevron with the accessible name "Collapse KMS sidebar") sits at the top of the sub-sidebar; clicking it tucks the file tree away and replaces it with a thin clickable **reopen edge-strip** (hover reveals a chevron; click brings the tree back). The collapsed/expanded state persists across launches via the `integrationSidebarHidden` setting (default `false` — expanded). A rebindable shortcut **`Ctrl+Alt+\`** ("Toggle KMS sidebar", editable at Settings → Keyboard Shortcuts) toggles it too, and is inert anywhere except the KMS screen. (This is separate from the right-pane drawer's **Toggle side panel** button in step 4 — that hides Outline/Backlinks/Tags/Bookmarks; this hides the whole file-tree column.)

It is **desktop-only**: on a phone the KMS panel uses its own Files/Sessions drill-down (`MobileKmsPanel`), so the collapse control never renders there. It is also **KMS-specific by opt-in**: the collapse is driven by a `sidebarCollapsible` flag on the KMS integration's UI manifest, so the other routed sub-sidebars (Tools, Skills, Alarms, AI Coaching) are unaffected — setting that flag on another integration is the only change needed to make its sub-sidebar collapsible too.

**How it's wired (repo detail).** The collapse button lives in [KmsSubSidebar.tsx](/src/renderer/src/features/kms/KmsSubSidebar.tsx) (desktop render path only, after the mobile early-return) and sets `integrationSidebarHidden`. [DashboardDesktopLayout.tsx](/src/renderer/src/features/dashboard/DashboardDesktopLayout.tsx) swaps the routed sub-sidebar for the shared `SidebarEdgeGutter` reopen strip when `shouldShowIntegrationReopenGutter(activeIntegration, integrationSidebarHidden)` is true ([integration-sidebar-collapse.ts](/src/renderer/src/features/dashboard/integration-sidebar-collapse.ts); the gutter side follows the `sidebarsRight` layout). The setting is a flat appearance setting ([appearance-settings.ts](/src/shared/types/settings/appearance-settings.ts)); the `toggleKmsSidebar` shortcut ([core.keybindings.ts](/src/shared/keybindings/core.keybindings.ts)) resolves the active integration via `resolveActiveIntegration` and no-ops unless it is collapsible. Invariants are locked by [integration-sidebar-collapse.test.ts](/tests/unit/features/dashboard/integration-sidebar-collapse.test.ts) (scope guard — a non-collapsible integration never collapses), [KmsSubSidebar-collapse.test.tsx](/tests/unit/features/kms/KmsSubSidebar-collapse.test.tsx) (desktop-only button + toggle), and [kms-ui-registry.test.ts](/tests/unit/integrations/kms-ui-registry.test.ts) (the `sidebarCollapsible` flag on KMS).

### Sorting the file tree

The sort order is chosen from a **sort dropdown** in the file-tree header ([FileTreeSortMenu.tsx](/src/renderer/src/features/kms/tree/FileTreeSortMenu.tsx)); the ordering itself is a pure comparator in [tree-builder.ts](/src/renderer/src/features/kms/tree/tree-builder.ts) (`compareNodes`). **Folders always sort above files** at the same level regardless of mode; within each group the chosen comparator applies, with the note name as the final stable tiebreak. A folder is synthetic (KMS has no folder entity — folders are derived from note paths), so it carries no date of its own: for the three time-based sorts each folder takes an **aggregate** time = the most-recent among the notes it contains (recursively), so the folder group orders by freshest content — the folder you touched most recently rises to the top — instead of collapsing to a name tiebreak. Six modes:

- **Name (A→Z)** / **Name (Z→A)** — locale-compared, case-insensitive, numeric-aware.
- **Modified** — most-recently-_edited_ on top (`updated_at`, which mirrors the note file's **real on-disk modification time**, so the order matches what you'd see in Explorer / the KMS desktop app / Obsidian). Opening a note to _read_ it (no edit) does not move it.
- **Created** — most-recently-_created_ on top (`created_at`, the note file's **real on-disk creation time**).
- **Recently opened** — most-recently-_opened_ on top (`last_opened_at` desc). A note's "opened" time is stamped **every time you open it** in the editor — including clicking back to an already-open tab — so this answers "what was I just looking at." Notes you've **never opened** sink to the bottom (alphabetical among themselves), and **before you've opened anything in a vault the tree is simply alphabetical** (every `last_opened_at` is null, so the comparator falls through to name). Restoring your open tabs on app launch does **not** count as opening — otherwise a relaunch would scramble the order.
- **Manual** — your drag-to-reorder arrangement (persisted in localStorage only; notes you haven't dragged fall back to `updated_at` desc). See the [tree-move contract](/.claude/memory/contracts/kms-tree-move-contract.md).

**How "Recently opened" is wired (repo detail).** The open-note action in [kms-store.ts](/src/renderer/src/features/kms/kms-store.ts) optimistically bumps the in-memory note's `lastOpenedAt` (so the tree reorders instantly) and fires a fire-and-forget `KMS_NOTE_TOUCH_OPENED` (`kms:note-touch-opened`) IPC; its handler ([kms-handlers.ts](/src/main/ipc/kms-handlers.ts)) stamps `last_opened_at = <now ISO>` on the row via `touchNoteOpened` ([queries-kms/notes.ts](/src/main/db/queries-kms/notes.ts)). The column lives on `nothari_notes` (migration `20260620230000-kms-notes-last-opened-at-column`), so the opened-time persists across restarts and is shared to the mobile/web client exactly like Modified/Created. The indexer's note-upsert **never writes the column**, so an external edit or a file-watcher reindex can't wipe an opened-time — regression-locked in [queries-kms.test.ts](/tests/unit/db/queries/queries-kms.test.ts). The sort _selection_ is per-device (localStorage), like every other mode; the _opened-times_ are shared via the DB so the ordering is consistent everywhere.

**How "Modified" / "Created" get their dates (repo detail).** The indexer derives both timestamps from the file itself, not from the moment Omniscio scanned it: `noteTimestampsFromStat(stat, now)` in [indexer.ts](/src/main/services/kms/indexer.ts) maps the file's `mtime` → `updated_at` and `birthtime` → `created_at` (read off the same `fs.stat` the indexer already does for the size check), and `upsertNote` ([queries-kms/notes.ts](/src/main/db/queries-kms/notes.ts)) writes those through — `created_at` is write-once unless the indexer supplies the file's birthtime to heal a row. So the two date sorts mirror the real files and **survive a reindex** (cold start / vault re-open re-derives the same dates). Before this fix the reindex stamped `updated_at`/`created_at` with `now`, so a full rescan collapsed **every** note to within milliseconds of the scan and the Modified/Created sorts degenerated to directory-walk-then-alphabetical order — most visible on an imported vault, where every note claimed the same "modified" minute. Self-write echoes are unaffected: a recognized autosave echo still re-indexes metadata only and **preserves** `updated_at` (it never reaches the time-writing path), so typing doesn't churn the sort — see the [KMS self-write echo contract](/.claude/memory/contracts/kms-self-write-echo-contract.md).

**How folders get a sort time (repo detail).** Folder nodes are built on the fly from note paths in `buildTree` ([tree-builder.ts](/src/renderer/src/features/kms/tree/tree-builder.ts)) with no timestamps of their own. A post-order pass `computeFolderTimestamps` then rolls each folder's `updatedAt` / `createdAt` / `lastOpenedAt` up to the most-recent value among its descendants (a folder holding only never-opened notes keeps a null opened-time, so it sorts last, just like a never-opened file). Before this, folder nodes carried null timestamps and the three time-based sorts silently fell back to alphabetical for folders while the files beside them reordered by date — now locked by [tree-tree.test.ts](/tests/unit/features/kms/tree/tree-tree.test.ts) "folders sort by their freshest contained note".

### Adding a note — the tree scrolls to it

Adding a blank page — the **+** button in the file-tree header, or right-click a row / blank area → **New note here** — creates an `Untitled` note, opens it in the editor, **and scrolls the file tree to it so you always see where it landed.** This matters because the sort (above) decides where a fresh `Untitled` sorts: under **Modified** / **Created** it lands at the top and is already in view, but under **Name (A→Z)** it sorts down near the bottom, which in a large vault is off-screen — so the tree scrolls it into view. The new page is also **highlighted** as the open note. If you create the note inside a **collapsed** folder (right-click that folder → New note here), the folder expands first so the new page is visible. The scroll only happens when the row isn't already visible (no jump when it already is), and it works the same in the mobile notes drawer (which reuses the same file tree). You can also start a new page from the **+** at the end of the open-tabs strip (on both the desktop and mobile tab strips): it creates the same `Untitled` note (auto-numbered on repeat) and opens it as a new tab — the tab-bar **+** does create-and-open only, so the new note is still **highlighted** in the tree once it appears, but it doesn't force the tree to scroll the way the header **+** does.

**How it's wired (repo detail).** On a successful `kms:note-create`, [FileTreePanel.tsx](/src/renderer/src/features/kms/tree/FileTreePanel.tsx) `handleNewNote` records the handler's **resolved** path (so an auto-numbered `Untitled 2.md` is the one revealed), calls `expandFolders(ancestorFolders(path))` to open any collapsed ancestors, and sets a `revealPath` state. Because the new row only enters the tree after the async `KMS_NOTE_CHANGED` → debounced `loadTree` reload, a reveal effect watches the flat rows and, once the row appears, calls `virtualizer.scrollToIndex(findRowIndex(flat, path), { align: 'auto' })` (`align:'auto'` = scroll only when off-screen; instant, so no reduced-motion concern). The open-note highlight (`KMS_TREE_ROW_ACTIVE`) keys on the active tab's `notePath` (not the tab id), so the just-added page stands out. Pinned by N5/N6 of the [KMS new-note contract](/.claude/memory/contracts/kms-new-note-contract.md) ([FileTreePanel.reveal.test.tsx](/tests/unit/features/kms/tree/FileTreePanel.reveal.test.tsx)).

## Related

The editor's own details — its toolbar, slash commands, tables, footnotes, syntax highlighting and the rest — are on [KMS — the editor](kms-part-2.md). Appearance and finding things are on [KMS — appearance and search](kms-part-3.md); images and links on [KMS — images, media and links](kms-part-4.md); the AI features, extra windows and publishing on [KMS — AI, windows and sharing](kms-part-5.md); and mobile, crash recovery and the internals on [KMS — mobile, sync and internals](kms-part-6.md).

- The **Vault Overview** — a searchable/sortable inventory table of every note, plus the `kms_inventory` read-only agent tool — is documented at [kms-vault-overview.md](kms-vault-overview.md).
- The IPC channels live in [/src/shared/ipc-channels/index.ts](/src/shared/ipc-channels/index.ts) — Zod schemas in [/src/shared/ipc-schemas.ts](/src/shared/ipc-schemas.ts), response types in [/src/shared/ipc-types.ts](/src/shared/ipc-types.ts), domain types in [/src/shared/kms-types.ts](/src/shared/kms-types.ts) (`KmsUnifiedImageHit`, `KMS_UNIFIED_SEARCH_IMAGES_LIMIT = 8`, `KmsUnifiedSearchKindFilter`).
- The settings field is part of `AppSettings` in [/src/shared/types.ts](/src/shared/types.ts); the matching Zod field is in `updateSettingsSchema`. Settings search entry: `kms-enabled`.
- The integration is registered in [/src/shared/integration-registry.ts](/src/shared/integration-registry.ts) with `kind: 'amc-builtin'` so the integration-completeness CI lint test will catch any missing wiring claim.
- The Phase 2 E2E happy-path lives at [/tests/e2e/ui/30-kms.spec.ts](/tests/e2e/ui/30-kms.spec.ts), using the helpers in [/tests/e2e/helpers/kms-helpers.ts](/tests/e2e/helpers/kms-helpers.ts) and the tmpdir-isolated vault fixture at [/tests/e2e/fixtures/kms-vault-fixture.ts](/tests/e2e/fixtures/kms-vault-fixture.ts).
- Phase 5 image integration tests: [/tests/integration/kms-asset-dedup.test.ts](/tests/integration/kms-asset-dedup.test.ts), [/tests/integration/kms-asset-refcount.test.ts](/tests/integration/kms-asset-refcount.test.ts), [/tests/integration/kms-image-analyzer.test.ts](/tests/integration/kms-image-analyzer.test.ts), [/tests/integration/kms-image-ipc.test.ts](/tests/integration/kms-image-ipc.test.ts), [/tests/integration/kms-images-migration-v215.test.ts](/tests/integration/kms-images-migration-v215.test.ts), [/tests/integration/kms-unified-search-images.test.ts](/tests/integration/kms-unified-search-images.test.ts).
- The lazy-load contract is pinned by [/tests/unit/build-output/kms-view-lazy-load.test.ts](/tests/unit/build-output/kms-view-lazy-load.test.ts).
- Phase 5 implementation plan: [/docs/plans/2026-05-16-nothari-phase-5-images-media.md](../plans/2026-05-16-nothari-phase-5-images-media.md).
