2026-02-20 22:55:49 +01:00
|
|
|
pub mod ai_chat;
|
2026-02-17 12:05:39 +01:00
|
|
|
pub mod frontmatter;
|
2026-02-15 12:54:11 +01:00
|
|
|
pub mod git;
|
2026-02-22 17:40:20 +01:00
|
|
|
pub mod github;
|
2026-02-28 20:11:52 +01:00
|
|
|
pub mod mcp;
|
2026-02-22 18:44:54 +01:00
|
|
|
pub mod menu;
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
pub mod search;
|
2026-02-22 13:33:32 +01:00
|
|
|
pub mod settings;
|
feat: vault-native theming system with live reload (#154)
* feat: add theming system design file with 3 frames
Design frames for Settings > Appearance panel, Command Palette
theme switching, and live theme editing flow visualization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Rust backend for vault-native theming system
Theme files live in _themes/*.json with colors, typography, and spacing
sections. Vault-level settings (.laputa/settings.json) store the active
theme. Built-in themes (default, dark, minimal) are seeded on vault
creation. All 6 Tauri commands registered: list_themes, get_theme,
get_vault_settings, save_vault_settings, set_active_theme, create_theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add frontend theme engine, settings UI, and command palette integration
Wire up useThemeManager hook to load themes via Tauri IPC, apply CSS
custom properties to :root, and support switching/creating themes.
Add Appearance section to Settings panel with color swatches and theme
cards. Register theme switch and create commands in the command registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use .to_string() instead of format! for string literal (clippy)
* style: cargo fmt on theme.rs
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:05:14 +01:00
|
|
|
pub mod theme;
|
2026-02-14 18:20:07 +01:00
|
|
|
pub mod vault;
|
|
|
|
|
|
2026-02-28 19:31:50 +01:00
|
|
|
use std::borrow::Cow;
|
refactor: split vault.rs into focused submodules
Break the 2338-line vault.rs into vault/ directory with 6 focused modules:
- mod.rs: core types (VaultEntry, Frontmatter), parse_md_file, scan_vault
- parsing.rs: text processing (snippet extraction, markdown stripping, date parsing)
- cache.rs: git-based incremental vault caching
- trash.rs: purge_trash with flat iterator chain
- rename.rs: note renaming with wikilink updates
- image.rs: attachment saving with filename sanitization
Also moves frontmatter update/delete wrappers to frontmatter.rs where they
belong, and converts public functions to accept &Path instead of &str.
CodeScene scores: mod.rs 10.0, parsing.rs 9.68, cache.rs 9.68,
trash.rs 9.38, rename.rs 9.68, image.rs 10.0 (all above 9.0 target).
All 169 Rust tests pass, 86.28% line coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:16:21 +01:00
|
|
|
use std::path::Path;
|
2026-02-28 20:11:52 +01:00
|
|
|
use std::process::Child;
|
|
|
|
|
use std::sync::Mutex;
|
refactor: split vault.rs into focused submodules
Break the 2338-line vault.rs into vault/ directory with 6 focused modules:
- mod.rs: core types (VaultEntry, Frontmatter), parse_md_file, scan_vault
- parsing.rs: text processing (snippet extraction, markdown stripping, date parsing)
- cache.rs: git-based incremental vault caching
- trash.rs: purge_trash with flat iterator chain
- rename.rs: note renaming with wikilink updates
- image.rs: attachment saving with filename sanitization
Also moves frontmatter update/delete wrappers to frontmatter.rs where they
belong, and converts public functions to accept &Path instead of &str.
CodeScene scores: mod.rs 10.0, parsing.rs 9.68, cache.rs 9.68,
trash.rs 9.38, rename.rs 9.68, image.rs 10.0 (all above 9.0 target).
All 169 Rust tests pass, 86.28% line coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:16:21 +01:00
|
|
|
|
2026-02-20 22:55:49 +01:00
|
|
|
use ai_chat::{AiChatRequest, AiChatResponse};
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
use frontmatter::FrontmatterValue;
|
2026-02-26 20:14:46 +01:00
|
|
|
use git::{GitCommit, GitPullResult, LastCommitInfo, ModifiedFile};
|
2026-02-23 19:56:30 +01:00
|
|
|
use github::{DeviceFlowPollResult, DeviceFlowStart, GitHubUser, GithubRepo};
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
use search::SearchResponse;
|
2026-02-22 13:33:32 +01:00
|
|
|
use settings::Settings;
|
feat: vault-native theming system with live reload (#154)
* feat: add theming system design file with 3 frames
Design frames for Settings > Appearance panel, Command Palette
theme switching, and live theme editing flow visualization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Rust backend for vault-native theming system
Theme files live in _themes/*.json with colors, typography, and spacing
sections. Vault-level settings (.laputa/settings.json) store the active
theme. Built-in themes (default, dark, minimal) are seeded on vault
creation. All 6 Tauri commands registered: list_themes, get_theme,
get_vault_settings, save_vault_settings, set_active_theme, create_theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add frontend theme engine, settings UI, and command palette integration
Wire up useThemeManager hook to load themes via Tauri IPC, apply CSS
custom properties to :root, and support switching/creating themes.
Add Appearance section to Settings panel with color swatches and theme
cards. Register theme switch and create commands in the command registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use .to_string() instead of format! for string literal (clippy)
* style: cargo fmt on theme.rs
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:05:14 +01:00
|
|
|
use theme::{ThemeFile, VaultSettings};
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
use vault::{RenameResult, VaultEntry};
|
2026-02-14 18:20:07 +01:00
|
|
|
|
2026-02-28 19:31:50 +01:00
|
|
|
/// Expand a leading `~` or `~/` in a path string to the user's home directory.
|
|
|
|
|
/// Returns the original string unchanged if it doesn't start with `~` or if the
|
|
|
|
|
/// home directory cannot be determined.
|
|
|
|
|
fn expand_tilde(path: &str) -> Cow<'_, str> {
|
|
|
|
|
if path == "~" {
|
|
|
|
|
if let Some(home) = dirs::home_dir() {
|
|
|
|
|
return Cow::Owned(home.to_string_lossy().into_owned());
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(rest) = path.strip_prefix("~/") {
|
|
|
|
|
if let Some(home) = dirs::home_dir() {
|
|
|
|
|
return Cow::Owned(format!("{}/{}", home.to_string_lossy(), rest));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Cow::Borrowed(path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 18:20:07 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn list_vault(path: String) -> Result<Vec<VaultEntry>, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(&path);
|
|
|
|
|
vault::scan_vault_cached(Path::new(path.as_ref()))
|
2026-02-14 18:20:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-15 12:54:11 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_note_content(path: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(&path);
|
|
|
|
|
vault::get_note_content(Path::new(path.as_ref()))
|
2026-02-15 12:54:11 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 14:48:21 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn save_note_content(path: String, content: String) -> Result<(), String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(&path);
|
2026-02-23 14:48:21 +01:00
|
|
|
vault::save_note_content(&path, &content)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 12:54:11 +01:00
|
|
|
#[tauri::command]
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
fn update_frontmatter(
|
|
|
|
|
path: String,
|
|
|
|
|
key: String,
|
|
|
|
|
value: FrontmatterValue,
|
|
|
|
|
) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(&path);
|
refactor: split vault.rs into focused submodules
Break the 2338-line vault.rs into vault/ directory with 6 focused modules:
- mod.rs: core types (VaultEntry, Frontmatter), parse_md_file, scan_vault
- parsing.rs: text processing (snippet extraction, markdown stripping, date parsing)
- cache.rs: git-based incremental vault caching
- trash.rs: purge_trash with flat iterator chain
- rename.rs: note renaming with wikilink updates
- image.rs: attachment saving with filename sanitization
Also moves frontmatter update/delete wrappers to frontmatter.rs where they
belong, and converts public functions to accept &Path instead of &str.
CodeScene scores: mod.rs 10.0, parsing.rs 9.68, cache.rs 9.68,
trash.rs 9.38, rename.rs 9.68, image.rs 10.0 (all above 9.0 target).
All 169 Rust tests pass, 86.28% line coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:16:21 +01:00
|
|
|
frontmatter::update_frontmatter(&path, &key, value)
|
2026-02-15 12:54:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn delete_frontmatter_property(path: String, key: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(&path);
|
refactor: split vault.rs into focused submodules
Break the 2338-line vault.rs into vault/ directory with 6 focused modules:
- mod.rs: core types (VaultEntry, Frontmatter), parse_md_file, scan_vault
- parsing.rs: text processing (snippet extraction, markdown stripping, date parsing)
- cache.rs: git-based incremental vault caching
- trash.rs: purge_trash with flat iterator chain
- rename.rs: note renaming with wikilink updates
- image.rs: attachment saving with filename sanitization
Also moves frontmatter update/delete wrappers to frontmatter.rs where they
belong, and converts public functions to accept &Path instead of &str.
CodeScene scores: mod.rs 10.0, parsing.rs 9.68, cache.rs 9.68,
trash.rs 9.38, rename.rs 9.68, image.rs 10.0 (all above 9.0 target).
All 169 Rust tests pass, 86.28% line coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:16:21 +01:00
|
|
|
frontmatter::delete_frontmatter_property(&path, &key)
|
2026-02-15 12:54:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_file_history(vault_path: String, path: String) -> Result<Vec<GitCommit>, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
let path = expand_tilde(&path);
|
2026-02-15 12:54:11 +01:00
|
|
|
git::get_file_history(&vault_path, &path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_modified_files(vault_path: String) -> Result<Vec<ModifiedFile>, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-15 12:54:11 +01:00
|
|
|
git::get_modified_files(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_file_diff(vault_path: String, path: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
let path = expand_tilde(&path);
|
2026-02-15 12:54:11 +01:00
|
|
|
git::get_file_diff(&vault_path, &path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 22:27:18 +01:00
|
|
|
#[tauri::command]
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
fn get_file_diff_at_commit(
|
|
|
|
|
vault_path: String,
|
|
|
|
|
path: String,
|
|
|
|
|
commit_hash: String,
|
|
|
|
|
) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
let path = expand_tilde(&path);
|
2026-02-21 22:27:18 +01:00
|
|
|
git::get_file_diff_at_commit(&vault_path, &path, &commit_hash)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 12:54:11 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn git_commit(vault_path: String, message: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-15 12:54:11 +01:00
|
|
|
git::git_commit(&vault_path, &message)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 20:14:46 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_last_commit_info(vault_path: String) -> Result<Option<LastCommitInfo>, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-26 20:14:46 +01:00
|
|
|
git::get_last_commit_info(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
feat: auto-pull vault changes from Git (background sync + conflict handling) (#79)
* feat: add auto-pull vault sync with conflict handling
- Add git_pull, has_remote, get_conflict_files to Rust backend
- Add GitPullResult type and auto_pull_interval_minutes to Settings
- Create useAutoSync hook (pull on launch, focus, periodic interval)
- Update StatusBar with real sync status indicator
- Add pull interval setting to SettingsPanel
- Add mock handler for git_pull
- Update existing tests for new Settings field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for git pull, settings, and useAutoSync hook
- Add Rust tests: has_remote, git_pull (no_remote, up_to_date, updated),
get_conflict_files, parse_updated_files, GitPullResult serialization
- Add frontend tests: useAutoSync (mount pull, focus pull, conflict,
error, manual trigger, concurrent prevention, no_remote)
- Fix existing settings tests for new auto_pull_interval_minutes field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add auto-pull-vault wireframes
Frames showing: sync idle, syncing, conflict indicator,
settings sync section, and conflict toast notification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add auto_pull_interval_minutes to all Settings literals
Fix build error and test fixtures missing the new field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cargo fmt
* ci: re-trigger CI after flaky test
* ci: retrigger after disk space cleanup
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 01:55:16 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn git_pull(vault_path: String) -> Result<GitPullResult, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
feat: auto-pull vault changes from Git (background sync + conflict handling) (#79)
* feat: add auto-pull vault sync with conflict handling
- Add git_pull, has_remote, get_conflict_files to Rust backend
- Add GitPullResult type and auto_pull_interval_minutes to Settings
- Create useAutoSync hook (pull on launch, focus, periodic interval)
- Update StatusBar with real sync status indicator
- Add pull interval setting to SettingsPanel
- Add mock handler for git_pull
- Update existing tests for new Settings field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for git pull, settings, and useAutoSync hook
- Add Rust tests: has_remote, git_pull (no_remote, up_to_date, updated),
get_conflict_files, parse_updated_files, GitPullResult serialization
- Add frontend tests: useAutoSync (mount pull, focus pull, conflict,
error, manual trigger, concurrent prevention, no_remote)
- Fix existing settings tests for new auto_pull_interval_minutes field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add auto-pull-vault wireframes
Frames showing: sync idle, syncing, conflict indicator,
settings sync section, and conflict toast notification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add auto_pull_interval_minutes to all Settings literals
Fix build error and test fixtures missing the new field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cargo fmt
* ci: re-trigger CI after flaky test
* ci: retrigger after disk space cleanup
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 01:55:16 +01:00
|
|
|
git::git_pull(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 12:54:11 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn git_push(vault_path: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-15 12:54:11 +01:00
|
|
|
git::git_push(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 22:55:49 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
async fn ai_chat(request: AiChatRequest) -> Result<AiChatResponse, String> {
|
|
|
|
|
ai_chat::send_chat(request).await
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 10:07:02 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn save_image(vault_path: String, filename: String, data: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-21 10:07:02 +01:00
|
|
|
vault::save_image(&vault_path, &filename, &data)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 21:31:47 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn copy_image_to_vault(vault_path: String, source_path: String) -> Result<String, String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
vault::copy_image_to_vault(&vault_path, &source_path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:13:03 +01:00
|
|
|
#[tauri::command]
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
fn rename_note(
|
|
|
|
|
vault_path: String,
|
|
|
|
|
old_path: String,
|
|
|
|
|
new_title: String,
|
|
|
|
|
) -> Result<RenameResult, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
let old_path = expand_tilde(&old_path);
|
2026-02-21 19:13:03 +01:00
|
|
|
vault::rename_note(&vault_path, &old_path, &new_title)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 17:14:02 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn purge_trash(vault_path: String) -> Result<Vec<String>, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-21 17:14:02 +01:00
|
|
|
vault::purge_trash(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 15:24:57 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn migrate_is_a_to_type(vault_path: String) -> Result<usize, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path);
|
2026-02-23 15:24:57 +01:00
|
|
|
vault::migrate_is_a_to_type(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 12:49:17 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn create_getting_started_vault(target_path: Option<String>) -> Result<String, String> {
|
|
|
|
|
let path = match target_path {
|
2026-02-28 19:31:50 +01:00
|
|
|
Some(p) if !p.is_empty() => expand_tilde(&p).into_owned(),
|
2026-02-28 13:33:09 +01:00
|
|
|
_ => vault::default_vault_path()?.to_string_lossy().to_string(),
|
2026-02-28 12:49:17 +01:00
|
|
|
};
|
|
|
|
|
vault::create_getting_started_vault(&path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn check_vault_exists(path: String) -> bool {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(&path);
|
2026-02-28 12:49:17 +01:00
|
|
|
vault::vault_exists(&path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_default_vault_path() -> Result<String, String> {
|
|
|
|
|
vault::default_vault_path().map(|p| p.to_string_lossy().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 15:14:21 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn batch_archive_notes(paths: Vec<String>) -> Result<usize, String> {
|
|
|
|
|
let mut count = 0;
|
|
|
|
|
for path in &paths {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(path);
|
|
|
|
|
frontmatter::update_frontmatter(&path, "Archived", FrontmatterValue::Bool(true))?;
|
2026-02-27 15:14:21 +01:00
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
Ok(count)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn batch_trash_notes(paths: Vec<String>) -> Result<usize, String> {
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
|
|
|
|
let mut count = 0;
|
|
|
|
|
for path in &paths {
|
2026-02-28 19:31:50 +01:00
|
|
|
let path = expand_tilde(path);
|
|
|
|
|
frontmatter::update_frontmatter(&path, "Trashed", FrontmatterValue::Bool(true))?;
|
2026-02-28 20:21:23 +01:00
|
|
|
frontmatter::update_frontmatter(
|
|
|
|
|
&path,
|
|
|
|
|
"Trashed at",
|
|
|
|
|
FrontmatterValue::String(now.clone()),
|
|
|
|
|
)?;
|
2026-02-27 15:14:21 +01:00
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
Ok(count)
|
|
|
|
|
}
|
|
|
|
|
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn update_menu_state(app_handle: tauri::AppHandle, has_active_note: bool) -> Result<(), String> {
|
|
|
|
|
menu::set_note_items_enabled(&app_handle, has_active_note);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 13:33:32 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_settings() -> Result<Settings, String> {
|
|
|
|
|
settings::get_settings()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn save_settings(settings: Settings) -> Result<(), String> {
|
|
|
|
|
settings::save_settings(settings)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 17:40:20 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
async fn github_list_repos(token: String) -> Result<Vec<GithubRepo>, String> {
|
|
|
|
|
github::github_list_repos(&token).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
async fn github_create_repo(
|
|
|
|
|
token: String,
|
|
|
|
|
name: String,
|
|
|
|
|
private: bool,
|
|
|
|
|
) -> Result<GithubRepo, String> {
|
2026-02-22 17:40:20 +01:00
|
|
|
github::github_create_repo(&token, &name, private).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn clone_repo(url: String, token: String, local_path: String) -> Result<String, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let local_path = expand_tilde(&local_path);
|
2026-02-22 17:40:20 +01:00
|
|
|
github::clone_repo(&url, &token, &local_path)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 19:56:30 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
async fn github_device_flow_start() -> Result<DeviceFlowStart, String> {
|
|
|
|
|
github::github_device_flow_start().await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn github_device_flow_poll(device_code: String) -> Result<DeviceFlowPollResult, String> {
|
|
|
|
|
github::github_device_flow_poll(&device_code).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn github_get_user(token: String) -> Result<GitHubUser, String> {
|
|
|
|
|
github::github_get_user(&token).await
|
|
|
|
|
}
|
|
|
|
|
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
#[tauri::command]
|
2026-02-26 13:40:27 +01:00
|
|
|
async fn search_vault(
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
vault_path: String,
|
|
|
|
|
query: String,
|
|
|
|
|
mode: String,
|
|
|
|
|
limit: Option<usize>,
|
|
|
|
|
) -> Result<SearchResponse, String> {
|
2026-02-28 19:31:50 +01:00
|
|
|
let vault_path = expand_tilde(&vault_path).into_owned();
|
2026-02-26 13:40:27 +01:00
|
|
|
let limit = limit.unwrap_or(20);
|
|
|
|
|
tokio::task::spawn_blocking(move || search::search_vault(&vault_path, &query, &mode, limit))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Search task failed: {}", e))?
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-28 20:11:52 +01:00
|
|
|
struct WsBridgeChild(Mutex<Option<Child>>);
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn register_mcp_tools(vault_path: String) -> Result<String, String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path).into_owned();
|
|
|
|
|
tokio::task::spawn_blocking(move || mcp::register_mcp(&vault_path))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Registration task failed: {e}"))?
|
|
|
|
|
}
|
|
|
|
|
|
feat: vault-native theming system with live reload (#154)
* feat: add theming system design file with 3 frames
Design frames for Settings > Appearance panel, Command Palette
theme switching, and live theme editing flow visualization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Rust backend for vault-native theming system
Theme files live in _themes/*.json with colors, typography, and spacing
sections. Vault-level settings (.laputa/settings.json) store the active
theme. Built-in themes (default, dark, minimal) are seeded on vault
creation. All 6 Tauri commands registered: list_themes, get_theme,
get_vault_settings, save_vault_settings, set_active_theme, create_theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add frontend theme engine, settings UI, and command palette integration
Wire up useThemeManager hook to load themes via Tauri IPC, apply CSS
custom properties to :root, and support switching/creating themes.
Add Appearance section to Settings panel with color swatches and theme
cards. Register theme switch and create commands in the command registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use .to_string() instead of format! for string literal (clippy)
* style: cargo fmt on theme.rs
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:05:14 +01:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn list_themes(vault_path: String) -> Result<Vec<ThemeFile>, String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
theme::list_themes(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_theme(vault_path: String, theme_id: String) -> Result<ThemeFile, String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
theme::get_theme(&vault_path, &theme_id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn get_vault_settings(vault_path: String) -> Result<VaultSettings, String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
theme::get_vault_settings(&vault_path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn save_vault_settings(vault_path: String, settings: VaultSettings) -> Result<(), String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
theme::save_vault_settings(&vault_path, settings)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn set_active_theme(vault_path: String, theme_id: String) -> Result<(), String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
theme::set_active_theme(&vault_path, &theme_id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn create_theme(vault_path: String, source_id: Option<String>) -> Result<String, String> {
|
|
|
|
|
let vault_path = expand_tilde(&vault_path);
|
|
|
|
|
theme::create_theme(&vault_path, source_id.as_deref())
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 13:31:25 +01:00
|
|
|
fn log_startup_result(label: &str, result: Result<usize, String>) {
|
|
|
|
|
match result {
|
|
|
|
|
Ok(n) if n > 0 => log::info!("{}: {} files", label, n),
|
|
|
|
|
Err(e) => log::warn!("{}: {}", label, e),
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run startup housekeeping on the default vault (purge old trash, migrate legacy frontmatter).
|
|
|
|
|
fn run_startup_tasks() {
|
|
|
|
|
let vault_path = dirs::home_dir()
|
|
|
|
|
.map(|h| h.join("Laputa"))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
if !vault_path.is_dir() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let vp_str = vault_path.to_str().unwrap_or_default();
|
|
|
|
|
log_startup_result(
|
|
|
|
|
"Purged trashed files on startup",
|
|
|
|
|
vault::purge_trash(vp_str).map(|d| d.len()),
|
|
|
|
|
);
|
|
|
|
|
log_startup_result(
|
|
|
|
|
"Migrated is_a to type on startup",
|
|
|
|
|
vault::migrate_is_a_to_type(vp_str),
|
|
|
|
|
);
|
2026-02-28 20:11:52 +01:00
|
|
|
|
|
|
|
|
// Register Laputa MCP server in Claude Code and Cursor configs
|
|
|
|
|
match mcp::register_mcp(vp_str) {
|
|
|
|
|
Ok(status) => log::info!("MCP registration: {status}"),
|
|
|
|
|
Err(e) => log::warn!("MCP registration failed: {e}"),
|
|
|
|
|
}
|
2026-02-25 13:31:25 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 14:45:11 +01:00
|
|
|
#[cfg(test)]
|
2026-02-28 19:31:50 +01:00
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expand_tilde_with_subpath() {
|
|
|
|
|
let home = dirs::home_dir().unwrap();
|
|
|
|
|
let result = expand_tilde("~/Documents/vault");
|
|
|
|
|
assert_eq!(result, format!("{}/Documents/vault", home.display()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expand_tilde_alone() {
|
|
|
|
|
let home = dirs::home_dir().unwrap();
|
|
|
|
|
let result = expand_tilde("~");
|
|
|
|
|
assert_eq!(result, home.to_string_lossy());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expand_tilde_noop_for_absolute_path() {
|
|
|
|
|
let result = expand_tilde("/usr/local/bin");
|
|
|
|
|
assert_eq!(result, "/usr/local/bin");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expand_tilde_noop_for_relative_path() {
|
|
|
|
|
let result = expand_tilde("some/relative/path");
|
|
|
|
|
assert_eq!(result, "some/relative/path");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expand_tilde_noop_for_tilde_in_middle() {
|
|
|
|
|
let result = expand_tilde("/home/~user/path");
|
|
|
|
|
assert_eq!(result, "/home/~user/path");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-23 21:58:10 +01:00
|
|
|
|
2026-02-14 18:20:07 +01:00
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
|
|
|
pub fn run() {
|
|
|
|
|
tauri::Builder::default()
|
2026-02-28 20:11:52 +01:00
|
|
|
.manage(WsBridgeChild(Mutex::new(None)))
|
2026-02-14 18:20:07 +01:00
|
|
|
.setup(|app| {
|
|
|
|
|
if cfg!(debug_assertions) {
|
|
|
|
|
app.handle().plugin(
|
|
|
|
|
tauri_plugin_log::Builder::default()
|
|
|
|
|
.level(log::LevelFilter::Info)
|
|
|
|
|
.build(),
|
|
|
|
|
)?;
|
2026-02-25 21:43:16 +01:00
|
|
|
// Open devtools automatically in debug builds
|
|
|
|
|
use tauri::Manager;
|
|
|
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
|
|
|
window.open_devtools();
|
|
|
|
|
}
|
2026-02-14 18:20:07 +01:00
|
|
|
}
|
2026-02-21 17:14:02 +01:00
|
|
|
|
2026-02-23 14:42:39 +01:00
|
|
|
app.handle().plugin(tauri_plugin_dialog::init())?;
|
|
|
|
|
|
2026-02-22 12:10:24 +01:00
|
|
|
#[cfg(desktop)]
|
|
|
|
|
{
|
feat: auto-build, GitHub Release, and in-app updater (#14)
* ci: auto-release workflow on merge to main
Rewrite .github/workflows/release.yml to trigger on every push to main
instead of manual tag pushes. The workflow now:
- Computes version as 0.YYYYMMDD.GITHUB_RUN_NUMBER
- Builds aarch64-apple-darwin and x86_64-apple-darwin in parallel
- Merges them into a universal binary using lipo
- Creates a universal .dmg and signed updater tarball
- Generates latest.json with per-arch and universal platform entries
- Publishes a GitHub Release with auto-generated release notes
- Updates a GitHub Pages release history site (gh-pages branch)
Product decisions:
- Universal binary approach: copy arm64 .app as base, lipo the main
executable, keep everything else from arm64 (shared frameworks are
architecture-independent). This is the standard Tauri pattern.
- Per-arch updater tarballs are also uploaded so the Tauri updater can
download the correct arch-specific build (smaller download).
- Release notes are auto-generated from git log since last tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: github pages with release history
Use peaceiris/actions-gh-pages@v4 to deploy a release history site.
The page fetches releases.json (also deployed) and renders each release
with date, notes, and download links for .dmg files.
This handles the gh-pages branch creation automatically on first run.
The page is available at https://refactoringhq.github.io/laputa-app/
Product decision: used fetch() to load releases.json at runtime instead
of inlining it, which is cleaner and avoids shell escaping issues with
release note content. The releases.json is deployed alongside index.html.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: in-app update notification UI
Replace the old window.confirm updater with a proper React-based
update notification system:
- useUpdater hook now exposes state machine (idle → available →
downloading → ready) and actions (startDownload, openReleaseNotes,
dismiss)
- UpdateBanner component renders at the top of the app shell:
- "Available" state: shows version, Release Notes link, Update Now
button, dismiss X
- "Downloading" state: animated spinner, progress bar with percentage
- "Ready" state: Restart Now button to apply the update
- Silently checks on startup after 3s delay; fails silently on
network errors or 404
- Release Notes link opens the GitHub Pages release history site
Product decisions:
- Banner at top of app (not a modal) — non-intrusive, visible but
not blocking. User can dismiss and continue working.
- Progress bar shows during download so user knows it's working.
- Separate "Restart Now" state after download so user controls when
the app restarts (they may have unsaved work).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: updater component tests
Rewrite useUpdater hook tests and add UpdateBanner component tests:
Hook tests (10 cases):
- Starts in idle state
- Does nothing when not in Tauri
- Checks for updates after 3s delay
- Stays idle when no update available
- Transitions to available when update found
- Handles missing release body gracefully
- Stays idle on network error (fails silently)
- Dismiss returns to idle
- openReleaseNotes opens correct URL
- startDownload transitions through downloading to ready
Component tests (10 cases):
- Renders nothing when idle
- Renders nothing on error
- Shows version and buttons when available
- Update Now calls startDownload
- Release Notes calls openReleaseNotes
- Dismiss button works
- Shows progress bar during download
- Shows 0% at start of download
- Shows restart button when ready
- Restart button calls restartApp
All 457 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: auto-build-release wireframes
Copy ui-design.pen as base. Frames to be added for:
1. Update notification banner (visible state) — horizontal bar at
top of app shell with version text, Release Notes link, Update
Now button, and dismiss X
2. Update download progress state — spinner icon, progress bar with
percentage, downloading text
3. "Restart to apply" state — green accent, version text, Restart
Now button
Note: Pencil editor was not available during this session. The base
design file is committed; frames will be added when the editor is
accessible. The implemented component (UpdateBanner.tsx) serves as
the source of truth for the design.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update ARCHITECTURE.md with release/update system
Add comprehensive documentation for:
- Release pipeline (4-phase workflow: version → build → release → pages)
- Versioning scheme (0.YYYYMMDD.RUN_NUMBER)
- Universal binary strategy (lipo merge)
- Updater endpoint and latest.json manifest
- In-app update UI state machine
- GitHub Pages release history site
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rustfmt formatting
* fix: rustfmt build.rs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:50:36 +01:00
|
|
|
app.handle()
|
|
|
|
|
.plugin(tauri_plugin_updater::Builder::new().build())?;
|
2026-02-22 12:10:24 +01:00
|
|
|
app.handle().plugin(tauri_plugin_process::init())?;
|
2026-02-24 22:20:33 +01:00
|
|
|
app.handle().plugin(tauri_plugin_opener::init())?;
|
2026-02-22 18:44:54 +01:00
|
|
|
menu::setup_menu(app)?;
|
2026-02-22 12:10:24 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-25 13:31:25 +01:00
|
|
|
run_startup_tasks();
|
2026-02-21 17:14:02 +01:00
|
|
|
|
2026-02-28 20:11:52 +01:00
|
|
|
// Spawn the MCP WebSocket bridge for the default vault
|
|
|
|
|
{
|
|
|
|
|
use tauri::Manager;
|
|
|
|
|
let vault_path = dirs::home_dir()
|
|
|
|
|
.map(|h| h.join("Laputa"))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let vp_str = vault_path.to_string_lossy().to_string();
|
|
|
|
|
match mcp::spawn_ws_bridge(&vp_str) {
|
|
|
|
|
Ok(child) => {
|
|
|
|
|
let state: tauri::State<'_, WsBridgeChild> = app.state();
|
|
|
|
|
*state.0.lock().unwrap() = Some(child);
|
|
|
|
|
}
|
|
|
|
|
Err(e) => log::warn!("Failed to start ws-bridge: {}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 18:20:07 +01:00
|
|
|
Ok(())
|
|
|
|
|
})
|
2026-02-15 12:54:11 +01:00
|
|
|
.invoke_handler(tauri::generate_handler![
|
|
|
|
|
list_vault,
|
|
|
|
|
get_note_content,
|
2026-02-23 14:48:21 +01:00
|
|
|
save_note_content,
|
2026-02-15 12:54:11 +01:00
|
|
|
update_frontmatter,
|
|
|
|
|
delete_frontmatter_property,
|
2026-02-21 19:13:03 +01:00
|
|
|
rename_note,
|
2026-02-15 12:54:11 +01:00
|
|
|
get_file_history,
|
|
|
|
|
get_modified_files,
|
|
|
|
|
get_file_diff,
|
2026-02-21 22:27:18 +01:00
|
|
|
get_file_diff_at_commit,
|
2026-02-15 12:54:11 +01:00
|
|
|
git_commit,
|
2026-02-26 20:14:46 +01:00
|
|
|
get_last_commit_info,
|
feat: auto-pull vault changes from Git (background sync + conflict handling) (#79)
* feat: add auto-pull vault sync with conflict handling
- Add git_pull, has_remote, get_conflict_files to Rust backend
- Add GitPullResult type and auto_pull_interval_minutes to Settings
- Create useAutoSync hook (pull on launch, focus, periodic interval)
- Update StatusBar with real sync status indicator
- Add pull interval setting to SettingsPanel
- Add mock handler for git_pull
- Update existing tests for new Settings field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for git pull, settings, and useAutoSync hook
- Add Rust tests: has_remote, git_pull (no_remote, up_to_date, updated),
get_conflict_files, parse_updated_files, GitPullResult serialization
- Add frontend tests: useAutoSync (mount pull, focus pull, conflict,
error, manual trigger, concurrent prevention, no_remote)
- Fix existing settings tests for new auto_pull_interval_minutes field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add auto-pull-vault wireframes
Frames showing: sync idle, syncing, conflict indicator,
settings sync section, and conflict toast notification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add auto_pull_interval_minutes to all Settings literals
Fix build error and test fixtures missing the new field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cargo fmt
* ci: re-trigger CI after flaky test
* ci: retrigger after disk space cleanup
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 01:55:16 +01:00
|
|
|
git_pull,
|
2026-02-20 22:55:49 +01:00
|
|
|
git_push,
|
2026-02-21 13:08:29 +01:00
|
|
|
ai_chat,
|
2026-02-21 17:14:02 +01:00
|
|
|
save_image,
|
2026-02-28 21:31:47 +01:00
|
|
|
copy_image_to_vault,
|
2026-02-22 13:33:32 +01:00
|
|
|
purge_trash,
|
2026-02-23 15:24:57 +01:00
|
|
|
migrate_is_a_to_type,
|
2026-02-27 15:14:21 +01:00
|
|
|
batch_archive_notes,
|
|
|
|
|
batch_trash_notes,
|
2026-02-22 13:33:32 +01:00
|
|
|
get_settings,
|
feat: populate macOS menu bar with native menus (File, Edit, View, Window) (#77)
* feat: populate macOS menu bar with File, Edit, View, Window menus
Add complete native macOS menu structure with all app actions:
- Laputa menu: About, Settings (Cmd+,), Hide/Quit
- File: New Note (Cmd+N), Quick Open (Cmd+P), Save (Cmd+S), Close Tab (Cmd+W)
- Edit: standard Undo/Redo/Cut/Copy/Paste/Select All
- View: Editor Only (Cmd+1), Editor+Notes (Cmd+2), All Panels (Cmd+3),
Toggle Inspector, Command Palette (Cmd+K)
- Window: Minimize, Maximize, Close Window
All accelerators registered via Tauri menu API. Menu events dispatched
to frontend via useMenuEvents hook. Save/Close Tab items dynamically
disabled when no note tab is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap Enter selection assertion in waitFor for effect re-registration
* fix: resolve rebase conflict markers in menu.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:05:24 +01:00
|
|
|
update_menu_state,
|
2026-02-22 17:40:20 +01:00
|
|
|
save_settings,
|
|
|
|
|
github_list_repos,
|
|
|
|
|
github_create_repo,
|
2026-02-23 14:42:39 +01:00
|
|
|
clone_repo,
|
2026-02-23 19:56:30 +01:00
|
|
|
github_device_flow_start,
|
|
|
|
|
github_device_flow_poll,
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
github_get_user,
|
2026-02-28 12:49:17 +01:00
|
|
|
search_vault,
|
|
|
|
|
create_getting_started_vault,
|
|
|
|
|
check_vault_exists,
|
2026-02-28 20:11:52 +01:00
|
|
|
get_default_vault_path,
|
feat: vault-native theming system with live reload (#154)
* feat: add theming system design file with 3 frames
Design frames for Settings > Appearance panel, Command Palette
theme switching, and live theme editing flow visualization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Rust backend for vault-native theming system
Theme files live in _themes/*.json with colors, typography, and spacing
sections. Vault-level settings (.laputa/settings.json) store the active
theme. Built-in themes (default, dark, minimal) are seeded on vault
creation. All 6 Tauri commands registered: list_themes, get_theme,
get_vault_settings, save_vault_settings, set_active_theme, create_theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add frontend theme engine, settings UI, and command palette integration
Wire up useThemeManager hook to load themes via Tauri IPC, apply CSS
custom properties to :root, and support switching/creating themes.
Add Appearance section to Settings panel with color swatches and theme
cards. Register theme switch and create commands in the command registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use .to_string() instead of format! for string literal (clippy)
* style: cargo fmt on theme.rs
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:05:14 +01:00
|
|
|
register_mcp_tools,
|
|
|
|
|
list_themes,
|
|
|
|
|
get_theme,
|
|
|
|
|
get_vault_settings,
|
|
|
|
|
save_vault_settings,
|
|
|
|
|
set_active_theme,
|
|
|
|
|
create_theme
|
2026-02-15 12:54:11 +01:00
|
|
|
])
|
2026-02-28 20:11:52 +01:00
|
|
|
.build(tauri::generate_context!())
|
|
|
|
|
.expect("error while building tauri application")
|
|
|
|
|
.run(|app_handle, event| {
|
|
|
|
|
use tauri::Manager;
|
|
|
|
|
if let tauri::RunEvent::Exit = event {
|
|
|
|
|
let state: tauri::State<'_, WsBridgeChild> = app_handle.state();
|
|
|
|
|
let mut guard = state.0.lock().unwrap();
|
|
|
|
|
if let Some(ref mut child) = *guard {
|
|
|
|
|
let _ = child.kill();
|
|
|
|
|
log::info!("ws-bridge child process killed on exit");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-02-14 18:20:07 +01:00
|
|
|
}
|