Plan: Milestone 7 - Daily Driver.
Done (2026-09-19)
All seven steps are implemented and verified on the web build; desktop builds and shares every code path (git runs in-process there). Commands are a contribution point: extensions list
{id, title, keybinding}, the shell keeps one registry with settings-overridable keybindings (Settings → Keybindings), menus show the effective shortcut, and a command palette (Ctrl+Shift+P) plus quick open (Ctrl+P,:linesuffix) drive them. File operations:Op::{CreateDir, Rename, Delete}on the folder source (delete =.moonkale/trash/), Explorer context menu with inline New File / New Folder / Rename, drag a row onto a folder to move, open documents follow renames with their unsaved text, the index follows too. Find & replace: CodeMirror’s search panel in every editor (Ctrl+F/Ctrl+H) and a workspace replace with a per-file preview (open documents stay unsaved, closed files are written through the source). LSP second half: completion,F2rename (multi-fileWorkspaceEdit),Ctrl+.code actions (withcodeAction/resolve),Shift+F12references — verified against rust-analyzer. Git: a Changes panel (branch, staged/unstaged, stage/unstage/discard, commit, log), diff views, Explorer and tab decorations, and history as a graph in the Graph panel; thegitCLI runs on desktop in-process and on the web server. Access token:MOONKALE_TOKENgates every server function and relay (HttpOnly cookie via/login, or a bearer), audit lines per relay call, login rate limit, and the server refuses a non-loopback bind without a token. Six new E2E suites (palette,files,replace,lsp2,git,auth) plus the seventeen earlier ones pass; 74 native tests; clippy/fmt clean.
Steps as executed
| # | step | outcome | notes |
|---|---|---|---|
| 1 | CommandContribution, Keybinding (parse/match/display), fuzzy_score; Extension::{commands, run_command}; ui::commands::Registry (built-ins + enabled extensions + settings.keybindings); palette + quick open; menus from the registry; Settings → Keybindings; agent.focus as the first extension command | ✅ 2 unit tests, E2E palette.mjs | ext-api.md, ui.md |
| 2 | core::Op::{CreateDir, Rename, Delete}; folder source (trash), RemoteSource unchanged (transactions pass through); Workspace::{create_dir, rename_node, delete_node} re-key documents; index refresh removes subtrees, walks new directories, re-resolves links; Explorer context menu, inline edits, drag-move, fs_epoch reloads | ✅ 2 unit tests, E2E files.mjs | project-fs.md, index.md |
| 3 | @codemirror/search in the bundle (Mod-h too), minimal-diff setText; Search panel replace row + preview; Workspace::{count_occurrences, replace_in_file} | ✅ E2E replace.mjs | markdown.md unaffected; editor-code.md |
| 4 | lsp: completion, rename, code_actions + resolve_code_action, references, WorkspaceEdit (parse both shapes, apply last-to-first, UTF-16 columns), strip_snippet; bundle: autocompletion + F2 / Mod-. / Shift-F12 hooks; panel: rename prompt, actions bar, references list; apply_workspace_edit | ✅ 2 unit tests, E2E lsp2.mjs (rust-analyzer) | lsp.md, editor-code.md |
| 5 | ext-api::git types + porcelain-v2/log parsers; extensions/git (cli runner, Changes panel, diff panel, history source, commands git.refresh/commit/history); api::git_run; desktop/web wiring; Workspace::vcs_status → Explorer + tab decorations; Graph panel draws Custom("git") sources | ✅ 4 unit tests, E2E git.mjs | git.md |
| 6 | api::auth: token, cookie login, bearer, audit target moonkale::audit, login rate limit, bind_mode guard; wired in web/src/main.rs | ✅ 2 unit tests, E2E auth.mjs (own server on 8091) | api.md |
| 7 | verify, log, vault | ✅ | this note |
What the user sees
Ctrl+Shift+Plists every command with its shortcut;Ctrl+Pjumps to a file (main.rs:12opens at line 12). Settings → Keybindings rebinds any of them (empty = unbound); menus follow.- Right-click in the Explorer: New File…, New Folder…, Rename…, Delete…, Open in Terminal. Drag a file onto a folder to move it. Deleted files sit in
<folder>/.moonkale/trash/<timestamp>/until you empty it. Ctrl+F/Ctrl+Hinside an editor; Search panel → Replace with… → Preview → Replace all.- In a Rust file with rust-analyzer: type to complete,
F2to rename across files (unsaved edits, save withCtrl+S),Ctrl+.for assists,Shift+F12for references. - Changes tab: stage with
+, discard with✕, commit withCtrl+Enter; click a change for its diff; Graph draws the last 200 commits and the files they touched in the Graph panel. Modified files are coloured in the Explorer and get a letter on their tab. - To expose the web server:
MOONKALE_TOKEN=<long random string> dx serve --addr 0.0.0.0 …(or the release binary behind a reverse proxy that setsX-Forwarded-Proto/X-Forwarded-For); open/loginonce per browser; scripts useAuthorization: Bearer. Without a token the server only starts on loopback.
Deviations from the plan
- Undo/Redo stay unbound in the registry — CodeMirror owns those keys; the menu entries dispatch the commands as before. Keys reserved by the browser (
Ctrl+R/T/L/C/V/X/A/Z/Y,F5,F12) are never forwarded by the document-level listener. - Delete is a move to
.moonkale/trash/on every platform rather than the OS trash: no extra dependency, identical on the server, and the folder already git-ignores.moonkale. - Rename changes the node id (ids derive from paths);
rename_nodere-keys the openDocumentand theviewsentry in place, so text, dirty state and version survive and the tab simply changes its title. Editors remount (the panel id contains the node id). - Workspace replace works on the files the search found, counting literal occurrences there (the search itself is BM25/semantic); open documents receive the replacement as an unsaved edit, closed files are written with a version check and re-indexed. The plan’s “diff per hit” became a per-file count with the open/closed distinction shown.
setTextin the CodeMirror bundle now replaces only the changed middle (common prefix/suffix kept) so the cursor and scroll survive external edits (renames, code actions, agent edits) — a small change that helps everywhere.- Code actions ask with an empty diagnostics context; rust-analyzer offers assists on the selection (extract into variable/constant/static/function) and quick fixes where it has them. Bare
Commands without an edit are filtered out (they would needworkspace/executeCommand). - Git via the CLI, porcelain v2 with
-zas planned;gixwas not needed. Diffs are rendered as coloured unified text rather than a CodeMirror merge view — simpler, andOpen fileis one click away. The history source draws commits ↔ files (touches) and commit ↔ parent edges; double-clicking a commit shows its subject in the status bar. gitandext-apitypes:apinow depends onext-api(no cycle) so the server function and the client shareGitRequest/GitResponse.- Client address in the audit log comes from
X-Forwarded-Forwhen a proxy sets it; dioxus’sservedoes not installConnectInfo, so a direct connection logs?and the login rate limit is then global (10 attempts/minute) rather than per address. - The Changes panel sits in the side tile by default (
explorer, search, links, git): a panel missing from the saved layout is attached and activated by the workbench, which would have hidden the Explorer on every start.
Problems hit (→ Problem Log)
- P-076 A task spawned in a component that unmounts in the same handler is dropped: the inline rename/new-file field set
edit = None(unmounting itself) and thenspawned the operation — nothing ran.spawn_foreverfor work that outlives the component. - P-077
#[serde(rename_all)]on an enum does not rename variant fields: the CodeMirror bridge’scodeActionsmessage carriedendLinewhile Rust expectedend_line; the deserialisation error ended the bridge’s receive loop, taking every later editor event with it. Fields are now renamed per variant and an unreadable message is logged and skipped instead of breaking the loop. - P-078 A completion source that never sends its request: the bundle registered the pending resolver but forgot to call
features.onCompletion— found with a standalone HTML page loading the bundle, which is now the quickest way to debug JS-side behaviour (~/.cache/moonkale-e2e/e2e/cm-test.html). - P-079 Hydration race after a form redirect: after
/loginthe app page is server-rendered first; clicking Open before hydration does nothing. Tests wait fornetworkidleafter the redirect; users are slower than Playwright. - P-080 cargo-check vs native diagnostics: the fixture’s type error is a cargo-check diagnostic; rust-analyzer republishes only native diagnostics on
didChange, so the M3 suite’s “fix clears the gutter” needed a save — and the code panel’sCtrl+Sdid not senddidSave(only the toolbar button did). Onesave_nowcallback now serves Ctrl+S, the menu and the button. - P-081 load in long batches:
phoneandauthtimed out once late in the 23-suite batch and pass alone and in pairs; rerun before debugging. - P-059 again: suites now also reset the git repository (
reset --hard <root commit>+clean) and the filesfiles.mjscreates. dx servestill needs a restart after asset rebuilds (P-070) — twice this milestone for the CodeMirror bundle.
Decisions worth keeping
- One registry, data in, keys out: commands are
{id, title, keybinding}; the enum stays as the payload for built-ins; settings rebind by id. - File operations are transactions on the source; the workspace re-keys documents and notifies the index; the Explorer reloads on
fs_epoch. - Every write that is not the user’s keystroke lands in the document first (replace, rename, code actions) and on disk only when the user saves — closed files are the one exception, and those go through
WriteTextwith a version check. - The LSP crate stays feature-neutral (
WorkspaceEdit,CodeAction,CompletionItemare ours); applying edits is the editor extension’s job. - Git is data through one function pointer (
WorkspaceConfig::git), so the panel is identical on desktop and web. - Token, not accounts; refuse non-loopback binds without one.
Verified
- Web (Firefox, Playwright, mock provider, fresh fixture per suite): all 23 suites PASS —
milestone1,menubar,session,graph,links-sqlite,terminal,typst,lsp,ladybug,agent,search-trace,settings,rich,agent-writes,flow,wasm-ext,phone,palette,files,replace,lsp2,git,auth(own token-mode server). - Native: 74 tests pass, 0 failed (3 ignored: live services); clippy and fmt clean;
cargo build -p desktop --features desktopandcargo check -p mobile --features mobilepass.
What to look at on desktop
Ctrl+Shift+P,Ctrl+Pwith:line; Settings → Keybindings.- Right-click a file in the Explorer; drag one into a folder; check
.moonkale/trash/. - Open a
.rsfile: type,F2,Ctrl+.,Shift+F12. - Changes tab on this repository: stage, commit, Graph.
MOONKALE_TOKEN=test dx serve --port 8080inpackages/web→/login.
Deferred to Milestone 8
Entity log (R-33) and collaboration (R-30/R-34), Postgres/Turso (R-19), TypeDB/Helix (R-26), 3D (R-27), browser-side wasm extensions (R-28), JS-free desktop (R-29), user accounts on the server, a CodeMirror merge view for diffs, git push/pull/fetch and branch creation, per-hunk staging, LSP signature help and semantic tokens, OS trash/keychain.