Adds a new Tauri command that moves a note file to the folder
corresponding to its new type when Is A is changed. Handles:
- folder creation, filename collision (-2 suffix), wikilink updates,
and no-op when already in the correct folder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three root causes identified and fixed:
1. JS semantics bug: `activeNoteContent ?? allContent[path]` used nullish
coalescing (`??`) which does NOT fall through on empty string ''. When
handleEditorChange temporarily overwrites tab.content with frontmatter-
only content during async content swaps, activeNoteContent becomes ''
and the fallback to allContent never triggers. Fix: change `??` to `||`.
2. Defence-in-depth: when body is still empty after fallback (Tauri mode
where allContent is {}), but wordCount > 0, the body field now includes
an explicit get_note instruction instead of being empty. This is more
reliable than the preamble instruction that Claude may skip.
3. Rust strip_frontmatter used `rest.find("---")` which matches `---`
anywhere in text (including inside frontmatter values like `title:
foo---bar`). Fixed to use `rest.find("\n---")` for line-boundary
matching. This ensures accurate wordCount for the fallback heuristic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The frontend writes 'archived: true' (lowercase) via handleUpdateFrontmatter,
but the Rust parser only recognized 'Archived' (titlecase). This caused all
notes archived from within Laputa to be read back as not archived — they
continued appearing in the sidebar and note list after restart.
Fix: add alias = "archived" to the serde attribute, matching the pattern
already used for 'trashed'/'Trashed'.
Regression tests added for both lowercase and titlecase variants.
Previous keypair was accidentally committed to git history.
New keypair generated, GitHub Secrets updated, pubkey rotated in tauri.conf.json.
Old key is now invalid for signing — any releases must use the new key.
- Add "Repair Vault" to command palette (Cmd+K → "Repair Vault")
- Add "Repair Vault" to macOS Vault menu bar
- Wire repair_vault Tauri command through App → useAppCommands → registry
- Add menu event handler for vault-repair
- Update MCP get_vault_context to include configFiles.agents content
- Add repair_vault mock handler for browser testing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "Install MCP Server" command was only enabled when status was
"not_installed", preventing users from re-registering when MCP got
removed or broken. Now the command is always available:
- Shows "Install MCP Server" when not installed
- Shows "Restore MCP Server" when already installed
- Added restore/fix/repair keywords for discoverability
- Context-aware toast: "installed" vs "restored"
- Menu bar label updated to "Restore MCP Server"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously called 'qmd update' and 'qmd embed' without arguments, which
updated all global qmd collections. If any other collection had a broken
path, the entire indexing would fail.
Now passes the vault name explicitly:
qmd update <vault_name>
qmd embed -c <vault_name>
This makes reindex independent of other qmd collections on the system.
- Add "Reindex Vault" command to command palette and menu bar
- Show "Indexed Xm ago" in status bar when idle, clickable to reindex
- After git pull with updates, auto-trigger incremental reindex
- Add lastIndexedTime state to useIndexing, populated from backend metadata
- Add triggerFullReindex to useIndexing (retryIndexing is now an alias)
- Add onSyncUpdated callback to useAutoSync
- Extract formatIndexedElapsed to utils/indexingHelpers.ts
- Tests: 20 new unit tests across 5 files, 2 Playwright smoke tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store last_indexed_commit and last_indexed_at in .laputa-index.json
after every successful full or incremental index. Include these in
IndexStatus so the frontend can display staleness. Add
needs_reindex_after_sync() helper that compares HEAD vs stored commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces raw string return from git_push with a structured GitPushResult
that classifies errors as rejected/auth_error/network_error/error, each
with an actionable user-facing message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ENTRY_DELETE_MAP and update map in frontmatterToEntryPatch were
missing the 'visible' key. When handleDeleteProperty or
handleUpdateFrontmatter was called for 'visible', the in-memory
VaultEntry was not updated, causing the sidebar to stay stale even
after the file on disk was correctly modified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On startup, reads hidden_sections from config/ui.config.md, creates or
updates Type notes with visible: false, then re-saves config without
hidden_sections. Idempotent and safe to run multiple times.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sidebar section visibility is now controlled entirely by the `visible`
property on Type notes. Removes all hidden_sections references from
Rust struct, TypeScript interface, config migration, mock data, and tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse `visible` boolean from Type note frontmatter (defaults to None/true
when absent). Add to SKIP_KEYS to exclude from relationships and properties.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These are machine-local files that should never be version controlled:
- .laputa-cache.json: contains absolute paths, changes on every machine
- .laputa/settings.json: per-machine UI settings
Fix: ensure_cache_excluded() now:
1. Adds both files to .git/info/exclude (git-level ignore, no .gitignore needed)
2. Runs `git rm --cached --ignore-unmatch` on vault open to un-track them
if they were committed in older vaults
This is idempotent and self-healing — existing vaults fix themselves
automatically on next app launch without any manual steps.
- Page size reduced from 30 → 20 commits per fetch
- Backend: add `skip` param to get_vault_pulse (git log --skip)
→ true pagination instead of re-fetching everything
- Frontend: IntersectionObserver sentinel at bottom of feed
→ auto-loads next page when user scrolls near end
- Append-only updates (no full re-render on load more)
- Add IntersectionObserver mock to test setup
Split on hyphens and underscores, capitalize each word, join with
spaces. E.g. monday-ideas → Monday Ideas, key-result → Key Result.
This prevents duplicate sidebar sections when folder name differs
from the Type file title.
Bump CACHE_VERSION to 5 to force rescan on existing caches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: git_uncommitted_files() used `git status --porcelain` which
reports new untracked directories as `?? theme/` instead of listing
individual files inside. The .md filter skipped directory entries, so
files in newly-created directories (theme/default.md, etc.) were
invisible to the cache system.
Fix: additionally use `git ls-files --others --exclude-standard` which
lists individual untracked files, resolving directories to their
contents.
Also:
- Add type/theme.md definition (icon: palette) to restore and getting-started vault
- Seed theme/*.md vault notes in getting-started vault
- Fix mock create_vault_theme to add entries for dev mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These files are machine-specific and should never be committed or cause
conflicts when syncing vaults across devices.
Changes:
- init_repo() now writes .gitignore with .laputa-cache.json and
.laputa/settings.json excluded before the first commit
- .DS_Store and common editor artifacts also excluded
- openConflictFileRef falls back to openLocalFile() for non-note files
so 'Open in editor' works for .json conflict files (opens in system
default app, e.g. TextEdit/VS Code)
- Removed stale git.rs (replaced by git/mod.rs from refactor)
- 2 new Rust tests for .gitignore creation behavior
macOS creates .DS_Store files in every folder which were being tracked
by git, causing constant conflicts when vaults are synced across machines.
init_repo() now writes a .gitignore before the first commit with:
- .DS_Store, .AppleDouble, .LSOverride
- ._ thumbnail files
- Common editor artifacts (.vscode/, .idea/, *.swp)
The file is only written if one doesn't already exist, so user-customized
.gitignore files are respected.
Two new Rust tests verify the behavior.
On fresh MacBook installs, the bundled qmd binary fails to run due to:
missing execute permissions, macOS quarantine attributes, and no fallback
when qmd is completely absent. This fix addresses all three issues:
- Runtime: ensure +x permissions and remove quarantine on bundled qmd
- Runtime: auto-install qmd via bun when binary not found anywhere
- Build: ad-hoc code-sign qmd and .dylib files in bundle-qmd.sh
- Build: create placeholder resource dirs so fresh clones build cleanly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the fragile auto-install-via-bun approach with a bundled qmd binary.
The build script (scripts/bundle-qmd.sh) compiles qmd into a standalone
binary using `bun build --compile`, then packages it with sqlite-vec native
extensions and a node-llama-cpp stub for keyword-only search.
Key changes:
- find_qmd_binary() now returns QmdBinary with path + work_dir, checks
bundled resource first (app bundle and dev mode), then system paths
- All Command::new(qmd_path) calls updated to use QmdBinary::command()
which sets the correct working directory for node_modules resolution
- Removed auto_install_qmd() and find_bun() — no longer needed
- Tauri config bundles resources/qmd/** into the app
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move Go Back + Go Forward from View menu to Go menu where they
logically belong. Move Toggle Raw Editor, Toggle AI Chat, and
Toggle Backlinks into the Note menu for better discoverability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new Pulse sidebar section that shows chronological git commit
history for the vault. Commits are grouped by day with message, time,
short hash (clickable GitHub link when remote configured), file list
with add/modify/delete status icons, and summary badges. Clicking a
file opens the note in the editor. Disabled with tooltip for non-git
vaults. Accessible via sidebar click or "Go to Pulse" command palette.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rust backend for vault-specific configuration stored as
config/ui.config.md — a regular vault note with YAML frontmatter.
Adds `view` field to VaultEntry for per-type view mode preferences.
Registers get_vault_config and save_vault_config Tauri commands.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing command palette commands to menu bar and reorganize into
logical groups: File, Edit, View, Go, Note, Vault, Window. New menus
expose navigation filters, note actions, vault management, themes, and
git operations. Context-sensitive items (archive, trash, diff, raw
editor, commit, conflicts) are greyed out when not applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove --tools "" restriction so the agent has native bash/read/write/edit
access. Set vault path as working directory for the subprocess.
Simplify MCP to 4 Laputa-specific tools (search_notes, get_vault_context,
get_note, open_note) — everything else is handled by native tools.
Add file operation detection from Write/Edit tool calls to auto-open
created notes and refresh modified notes in the UI. Enhanced tool call
labels show bash commands, file paths, and note names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
[[target]] now shows as "target" and [[target|alias]] shows as "alias"
in note list previews, instead of raw bracket syntax.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New restore_default_themes() Rust fn: seeds both _themes/ and theme/ dirs
- Per-file idempotent: never overwrites existing files with content
- Fixed ensure_vault_themes() to include minimal.md (was missing)
- New 'Restore Default Themes' command in Cmd+K Appearance group
- 3 new Rust tests + 4 new frontend tests
- 1676 frontend tests passed
The previous conflict detection only worked for merge-based pulls
(--no-rebase) but failed to detect pre-existing conflicts from
interrupted rebases or prior sessions. This fixes three root causes:
1. Rust: add is_rebase_in_progress/is_merge_in_progress/get_conflict_mode
helpers, and dispatch git_commit_conflict_resolution between
`git commit` (merge) and `git rebase --continue` (rebase)
2. Frontend: add startup conflict check via get_conflict_files before
pulling, so pre-existing conflicts are detected on app launch
3. App.tsx: handleOpenConflictResolver now fetches conflicts directly
when the cached list is empty, preventing the silent early-return
Also exposes get_conflict_files and get_conflict_mode as Tauri commands
so the frontend can independently check conflict state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>