Computed reverse relationships — purely frontend, no Rust changes.
The useReferencedBy hook scans all entries' relationships maps to find
those referencing the current note via frontmatter wikilinks.
References are grouped by relationship key (e.g. "via Belongs to",
"via Related to") and displayed between Relationships and Backlinks.
Architecture decision: "Referenced by" is read-only. To remove a
reverse reference, navigate to the source note. Deleting a note
automatically removes it from all computed reverse references.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two frames showing the colored wikilink feature:
1. Editor view with wikilinks colored per note type (red=Experiment,
yellow=Person/Event, purple=Responsibility, green=Topic)
2. Broken link state with muted color + dashed underline + reduced opacity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three frames showing the new bidirectional relationships UI:
1. Inspector with multiple references grouped by relationship type
2. Empty state (no references)
3. Many backlinks scenario with references from multiple relationship types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wikilinks in the editor now show the accent color of the target note's
type (Project=red, Person=yellow, Topic=green, etc.) instead of always
being blue. Broken links (target not found) show muted text with a
dashed underline.
- Extract wikilink color resolution to src/utils/wikilinkColors.ts
- Use getTypeColor from typeColors.ts (single source of truth)
- Support alias and pipe-syntax matching in findEntryByTarget
- Add wikilink--broken CSS class for non-existent targets
- Add 17 unit tests covering all color resolution cases
- Update mock data with wikilinks to various note types + broken link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows the before state (with duplicate is_a property row highlighted in
orange) and the after state (clean inspector with only the Type row).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rust Frontmatter struct now parses both 'type' (new) and 'Is A'/'is_a' (legacy),
with 'type' taking precedence via resolve_type()
- Added migrate_is_a_to_type() vault-wide migration (runs on startup, also exposed as Tauri command)
- Frontend: buildNoteContent and resolveNewType now emit 'type:' in YAML
- Inspector SKIP_KEYS hides 'is_a', 'Is A', 'type', 'title' from property list
(TypeRow already renders the type with its dedicated UI)
- frontmatterToEntryPatch handles both 'type' and legacy 'is_a' keys
- Mock data updated: all YAML content uses 'type:' instead of 'is_a:'/'Is A:'
- Tests updated to expect 'type:' in generated frontmatter
Product decision: internal VaultEntry field stays as `isA` (TS) / `is_a` (Rust)
to minimize blast radius. The user-facing change is the YAML key and inspector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two frames showing click vs Cmd+Click behavior:
1. Click — Replace Current Tab (note replaces active tab content)
2. Cmd+Click — Open New Tab (new tab created, original preserved)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Regular click on a note in NoteList now replaces the current tab content
instead of always creating a new tab. Cmd+Click (or Ctrl+Click) opens
in a new tab. If the note is already open in any tab, clicking just
switches to that tab regardless of modifier key.
- NoteItem: simplified to accept single onClickNote callback
- NoteList: routes click via metaKey/ctrlKey to onReplaceActiveTab or onSelectNote
- useTabManagement: handleReplaceActiveTab checks all tabs before replacing
- Extracted loadAndSetTab/isTabOpen/routeNoteClick helpers for code health
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- E2E test now verifies Cmd+S shortcut shows a save toast
- Added design/editor-save-bug.pen wireframe
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes the debounced auto-save (useAutoSave hook) which was causing the
editor to reload previous content. Replaces it with explicit save on
Cmd+S (⌘S), consistent with the git-based UX of the app.
- Removed useAutoSave hook and its debounce mechanism
- Added useSaveNote hook for direct persist-to-disk
- Added useEditorSave hook that manages pending content buffer + save
- Cmd+S now persists the current editor content immediately
- Rename-before-save: saves pending content before rename to prevent
the "Failed to rename note" error
- Updated E2E test to verify Cmd+S behavior
- 471 tests passing, code health gates passed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updated Inspector Abstraction docs to describe the new two-section
layout (editable properties + read-only Info section) and the
SKIP_KEYS filter. Updated .claude-done with task summary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The is_a frontmatter key was appearing both as the TypeRow badge and
as a regular editable property. Added both 'is_a' and 'Is A' variants
to SKIP_KEYS to prevent duplication (snake_case used in mock data,
space-separated used in real vault YAML).
Added test verifying is_a is skipped when TypeRow is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split the Properties panel into two visually distinct sections:
1. **Properties** (editable): frontmatter properties with interactive
hover background, cursor pointer, and click-to-edit. Includes Type
row, Status, Owner, tags, and all user-defined properties.
2. **Info** (read-only): derived metadata shown below a border
separator with an "Info" header. Uses muted color (--text-muted)
with no hover states or click interaction. Includes:
- Modified date
- Created date (new)
- Word count
- File size (new, formatted as B/KB/MB)
Product decisions:
- Info section uses --text-muted for both labels and values (more
dimmed than the --muted-foreground used by editable labels)
- Editable rows get rounded hover:bg-muted on the full row
- Type row stays at top of Properties as an identity element
- Added Created date and file Size to give more useful metadata
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two frames showing the visual distinction between editable properties
and read-only Info section:
- Frame 1: Full inspector with editable properties (hover states,
cursor pointer) and Info section (muted, non-interactive)
- Frame 2: Side-by-side comparison of editable vs read-only styling
Product decisions:
- Separate "Info" section for derived metadata (Modified, Created,
Words, Size)
- Info uses --text-muted color, no hover/click states
- Editable properties keep interactive styling (hover bg, pointer)
- Type row stays at top of Properties section as identity element
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix type annotations in App.tsx (setTabs callback)
- Cast restoreWikilinksInBlocks result in Editor.tsx for BlockNote compatibility
- Fix vi.fn() mock types in useAutoSave.test.ts
- Add E2E test verifying editor loads and renders note content
- Update .claude-done with editor-save-bug summary
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The editor was not persisting changes to disk. Edits would be lost
when closing and reopening a note.
Changes:
- Add save_note_content Tauri command (Rust) with read-only file check
- Add save_note_content mock handler for browser testing
- Add useAutoSave hook with 500ms debounce and flush-on-tab-switch
- Wire up BlockNote onChange → markdown serialization → auto-save
- Add restoreWikilinksInBlocks utility (reverse of injectWikilinks)
to convert wikilink nodes back to [[target]] before markdown export
- Extract shared walkBlocks helper to eliminate code duplication
- Suppress onChange during programmatic content swaps (tab switching)
- 12 new frontend tests + 5 new Rust tests (all 469 + 174 passing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 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>
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>
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>
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>
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>
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>