TitleField now uses var(--headings-h1-font-size/weight/line-height/letter-spacing)
instead of hardcoded 28px, matching the editor H1 exactly. Added margin-left: 8px
to align with BlockNote's bn-block-content offset. Fixed bn-editor max-width to
use the same CSS var as the title-section for consistent horizontal alignment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wikilinks ([[note name]]) are valid content in note snippets — they are
plain-text references, not raw markdown formatting. The test was failing
against demo-vault data containing wikilinks in renamed-title-xyz.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies that window focus events don't block the main thread for >500ms,
covering both single focus and rapid 5x focus (Cmd+Tab spam) scenarios.
Completes the regression test requirement for the git-commands-off-main-thread fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sync Tauri commands (git_pull, git_push, git_remote_status, reload_vault)
blocked the runtime thread during network I/O, freezing the UI for 2-3s
on every Cmd+Tab. Converted them to async with tokio::spawn_blocking.
Added 30s cooldown to focus-triggered git pull and theme settings reload
to prevent redundant work on rapid app switching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveRefs() and refsMatch() in noteListHelpers used a simple 2-pass
matching (path stem + filename stem), while the Inspector used the unified
resolveEntry() with 4-pass resolution (filename, alias, title, humanized
title). Notes matched only by title or alias were silently dropped from the
note list, causing incomplete relationship groups.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PropertyRow had py-0.5 (4px total) while InfoRow had no vertical padding,
making Properties rows taller than Info rows even with equal gap values.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WKWebView doesn't invalidate ::before/::after pseudo-element styles when
CSS custom properties change via inline styles alone — offsetHeight reflow
only triggers layout, not style recalculation. This caused bullet size and
bullet color (rendered via ::before on bulletListItem) to not update live
when editing a theme note and saving with Cmd+S.
Fix: after setting CSS vars as inline styles, also inject them into a
<style> element. Replacing <style> content forces a full style tree
invalidation in WebKit, covering pseudo-elements that reference var().
Also extract shouldDeactivate/deactivateTheme helpers from useThemeManager
to reduce cyclomatic complexity (CodeScene: 8.77 → 9.6).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two root causes:
1. noteListHooks used a stale selection.entry to build relationship groups —
now looks up the fresh entry from the entries array so relationship updates
propagate immediately.
2. frontmatterToEntryPatch didn't update entry.relationships — added
RelationshipPatch support so frontmatter changes also update the
relationships map in state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Changes NavItem moved from the top nav to the sidebar-secondary area,
so Playwright locators need to scope within the new data-testid.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changes and Pulse are git status UI, not content navigation. Moving them
out of the main top nav into a compact secondary area at the bottom of
the sidebar keeps the primary section focused on vault content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When push is rejected due to remote having newer commits, the bottom bar
now shows "Pull required" (orange). Clicking it pulls then auto-pushes.
Conflicted notes show an inline banner with "Keep mine" / "Keep theirs"
buttons. A new "Pull from Remote" command is available in Cmd+K and the
Vault menu. Clicking the sync badge opens a popup showing branch name,
ahead/behind counts, and a Pull button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add multi-window support: notes can be opened in dedicated secondary
Tauri windows with editor-only layout (no sidebar, no note list).
Triggers: Cmd+Shift+Click on notes, Cmd+K → "Open in New Window",
Cmd+Shift+O shortcut, Note → "Open in New Window" menu bar item.
Secondary windows have their own auto-save, theme, and wikilink
navigation. Closing a secondary window does not affect the main window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed property rows gap from gap-2 (8px) to gap-1.5 (6px) to match
the Info section's vertical density.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Labels and values each get w-1/2 so neither can squeeze the other.
Long values no longer cause short labels like "URL" to truncate to "U…".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Inbox shows notes without valid outgoing relationships (body wikilinks
or frontmatter refs), helping users find captured but unorganized notes.
Includes time-period filter pills (This week/month/quarter/All time),
Cmd+K command, and macOS menu bar entry. Broken wikilinks (targeting
non-existent notes) are not counted as relationships.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Content is automatically persisted 500ms after the last edit in both
BlockNote and raw editor modes. Cmd+S still works as immediate flush.
Tab close flushes any pending auto-save to prevent data loss.
Updated the "unsaved" tab indicator to show "Auto-saving…" with pulse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The theme live-reload mechanism was already working correctly — the
previous QA failures were caused by flawed test methodology (modifying
files on disk while the editor had them open, so Cmd+S overwrote with
stale content). Added unit and Playwright tests that verify ALL theme
properties (including bullet-size/color) update when editing the raw
editor buffer and saving. Removed old skipped test file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These properties have dedicated UI elsewhere (trash/archive banner, emoji picker)
and should not appear as generic editable properties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Note was explicitly excluded from collectActiveTypes() via `e.isA !== 'Note'`.
Removed the exclusion so Note appears like any other type. Untyped entries
(isA === null) now count as Note in both type collection and section filtering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire notifyThemeSaved through the frontmatter update chain so CSS
variables update immediately when:
- The user edits a theme note in raw mode and presses Cmd+S
- A frontmatter property is changed via the inspector panel
Also expose CodeMirror EditorView on the DOM for Playwright test access,
add unit tests for onNotePersisted, and a new Playwright smoke test
that verifies the full raw-editor → save → CSS-vars-update flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Topics used a separate 'topic' selection kind that only showed reverse
relatedTo matches. Now topics use 'entity' kind like all other types,
going through buildRelationshipGroups to show all frontmatter
relationships, children, events, referenced-by, and backlinks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add emoji icon rendering before the note title in TabBar, BreadcrumbBar,
and PinnedCard components to ensure consistent emoji display everywhere
a note title appears. Sidebar, NoteItem, wikilinks, relationships, and
backlinks already had emoji support from prior commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The first snippet test consistently fails because Virtuoso doesn't
render snippet elements in the initial viewport during smoke tests.
Remove the unreliable test; keep the formatting validation tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The generic .text-muted-foreground selector matched metadata elements
instead of the actual snippet div. Use .text-[12px] qualifier to target
only snippet elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move title-section inside a shared .editor-scroll-area wrapper so both
title and editor content center within the same scrollable context,
fixing alignment drift at wide widths caused by scrollbar offset.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Display the note's frontmatter emoji before its title in the editor
wikilink renderer, relationship LinkButtons, and backlinks panel for
consistent emoji visibility across the app.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Old tests typed into the H1 heading to trigger title rename — now
that H1 is decoupled from title, all rename flows go through TitleField.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
H1 headings are body content, not the title source of truth. Remove
useHeadingTitleSync hook and frontmatter-title-from-H1 logic in the
editor. Rust rename_note no longer modifies H1 in file content — only
updates the frontmatter title: field.
TitleField now uses optimistic UI: the new title displays immediately
after commit while the async file rename runs in background, preventing
UI freezes on slow filesystems or large vaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously restore/unarchive set `Trashed: false` / `archived: false` in
frontmatter. Now uses handleDeleteProperty to remove the fields entirely,
keeping frontmatter clean as if the note was never trashed/archived.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs fixed:
1. CodeMirror raw editor didn't reflect frontmatter changes (e.g. Trashed: true)
after trash/archive because useCodeMirror only used content as initial state.
Added content-sync effect with external-sync guard to prevent infinite loops.
2. Entry actions (trash/archive/restore/unarchive) showed "Property updated" toast
instead of contextual message because runFrontmatterAndApply overwrote it.
Added silent option to suppress toast when caller manages its own feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Repair Vault and app startup no longer create or recreate the _themes/
directory (legacy JSON theme store). All config files — theme notes,
theme.md, config.md, AGENTS.md — are seeded exclusively at vault root.
- Remove seed_default_themes() and all _themes/ creation paths
- Add migrate_legacy_themes_dir() to clean up _themes/ with only defaults
- Stop list_themes() from auto-creating _themes/ when absent
- Remove _themes from KEEP_FOLDERS and PROTECTED_FOLDERS (flatten-safe)
- Fix create_theme() to error instead of recreating _themes/
- Remove _themes/ seeding from create_getting_started_vault()
- Update all affected tests (642 Rust + 2253 frontend pass)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 tests covering: full emoji set (1800+), English name search,
continuous scroll, select/change/remove flow, command palette
integration, escape handling, and empty search state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace curated ~270 emoji subset with full Unicode set (1900+) via
unicode-emoji-json. Emoji search now works by English name (e.g.
"rocket" → 🚀). All emojis visible in continuous scroll with sticky
category headers and icon-based quick-nav tabs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rework feedback: filter pills row used py-1.5 padding instead of a
fixed 45px height, causing visual misalignment with the breadcrumb bar.
Replace padding with explicit h-[45px] to match exactly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing a type in the sidebar, the note list now shows filter pills
below the header to switch between Open, Archived, and Trashed notes.
Each pill shows a count badge. Bulk actions are context-aware: Trashed
filter offers Restore/Archive/Delete permanently, Archived filter offers
Unarchive/Trash. Cmd+K commands added for switching filters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
handleSelectNote skipped syncNoteTitle for tabs already open (early
return). Moved sync call into handleSelectNoteWithSync so it always
runs, even when switching to an existing tab via Cmd+P. Also removes
H1 fallback from extract_title — title now comes from frontmatter or
filename only, per the title/filename contract.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove owner, cadence, created_at, and created_time as hardcoded fields from
the Frontmatter struct — they don't drive app logic and belong in generic
properties. Owner and cadence values now flow through to the properties map
(including single-element array unwrapping). Creation date is sourced from
filesystem metadata (birthtime on macOS) instead of frontmatter.
Also fixes title extraction: add H1 heading extraction to extract_title()
(priority: frontmatter title → H1 → filename slug). Previously titles fell
back directly to filename when no frontmatter title was set.
StringOrList is retained for scalar Frontmatter fields as a defensive measure:
YAML values can arrive as single-element arrays (e.g. Status: [Active]) which
would cause entire deserialization to fail without it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every note can now have an optional emoji icon (frontmatter `icon` field).
The icon is displayed in the editor header, note list, search results,
and Quick Open. Includes command palette commands "Set Note Icon" and
"Remove Note Icon", plus full test coverage (Vitest + Playwright).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- handleSelectNote syncs title frontmatter before loading content
- handleSelectNoteWithSync reloads entry after open to update display title
- Added sync_note_title to mock handlers
- Fixed rapid-switching test to flush sync microtask
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New sync_title_on_open function: detects desync between title frontmatter
and filename, corrects it (filename wins as source of truth)
- Registered sync_note_title Tauri command
- rename_note now always writes title to frontmatter (not just when present)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Title is now sourced from the `title` frontmatter field with filename-
derived fallback (slug_to_title). H1 headings are treated as body content.
Added `title` to Frontmatter struct and SKIP_KEYS. Made title_to_slug
pub(super) for reuse across vault submodules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vault/mod.rs was 1820 lines with duplicated code already extracted to
entry.rs, frontmatter.rs, file.rs but never wired up. Slim mod.rs to
delegate to submodules. Score: 7.85 → 10.0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Repair Vault command now runs flatten_vault() and migrate_is_a_to_type()
before restoring themes and config, ensuring vaults adopt flat structure.
Also fixes config.md type definition to use `type: Type` instead of
legacy `Is A: Type`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DEFAULT_VAULTS was hard-coded to the main repo path, causing the Vault API
to read stale/duplicate files when running from worktrees. Now uses Vite's
define to inject the correct path at build time. Also fix theme heading
titles (remove redundant "Theme" suffix) and make smoke tests resilient
to Theme-type notes appearing first in the note list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The remote CodeScene API only updates after push + re-analysis, creating a
chicken-and-egg blocking loop. The local pre_commit_code_health_safeguard
already validates code health before commit. Pre-push now reports remote
scores for visibility without blocking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract scan_vault helpers (is_md_file, try_parse_md, scan_root_md_files, scan_protected_folders)
to eliminate deep nesting and reduce cyclomatic complexity. Break up large assertion blocks in tests.
Extract useEditorSetup and useRawModeWithFlush hooks from Editor component to reduce complexity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Theme vault notes now live at root as default-theme.md, dark-theme.md,
minimal-theme.md instead of in theme/ subdirectory. AGENTS.md holds full
content at root instead of stub+config/agents.md pattern. Adds migration
for legacy theme/ and config/ directories on startup and repair.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Editor mode is now stored as a global preference in VaultConfig instead of
being tracked per-tab. Toggling raw mode persists across tab switches and
app restarts via the editor_mode field in config/ui.config.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace per-field Option<String> with Option<StringOrList> for all string
fields in the Frontmatter struct (owner, cadence, status, icon, color,
sidebar_label, template, sort, view, trashed_at, created_at, created_time).
Previously, fields like Owner stored as YAML arrays (e.g. `Owner: [Luca]`)
caused serde to fail the entire Frontmatter deserialization, defaulting all
fields to None — including is_a, which broke the type badge display.
Now all string fields use StringOrList with into_scalar() normalization:
- Single-element array → unwrap to scalar
- Multi-element array → take first element
- Scalar → unchanged
- Empty array → None
- Absent → None
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers note creation via Cmd+N, unique naming, note selection, and
inspector rendering. Also documents frontmatterOps in ARCHITECTURE.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
39 tests for useNoteCreation covering creation, daily notes, templates,
optimistic revert, and unsaved cleanup. 12 tests for useNoteRename
covering rename operations, toast messages, and error handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useNoteActions.ts reduced from 213 to 125 lines by extracting frontmatter
helpers into frontmatterOps.ts and removing re-exports. Consumers now
import directly from the extracted modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The vault module split left the mod.rs VaultEntry struct missing fields
that were added in entry.rs but never wired in. Adds the missing fields,
populates them from frontmatter, adds status/owner/cadence to SKIP_KEYS,
and fixes conflicting test expectations from the incomplete merge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unused HashMap import from mod.rs, use contains() instead of
iter().any() in frontmatter.rs, add HashMap import to test module.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract VaultEntry struct to entry.rs (64 lines), YAML parsing to
frontmatter.rs (323 lines), and file I/O helpers to file.rs (59 lines).
Tests moved to mod_tests.rs. mod.rs reduced from 1679 to 189 lines,
now purely orchestration (parse_md_file, reload_entry, scan_vault).
All 612 tests pass, public API unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract note creation (CRUD, daily notes, types, optimistic persistence) into
useNoteCreation and rename operations into useNoteRename. useNoteActions now
composes both hooks plus frontmatter/navigation logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Frontmatter struct deserialization failed completely when any unknown
field had a list value (e.g. Owner: [Luca], Cadence: [Weekly]) because
serde expected a string but got an array. This caused unwrap_or_default()
to return all-None, losing type/status/archived for ~7000+ notes.
Two fixes:
1. Filter parse_frontmatter input to only known keys, preventing unknown
fields from causing deserialization failures
2. Change Owner and Cadence to StringOrList to handle both formats
Extract PropertyValueCells, TypeSelector, and AddPropertyForm into dedicated
files. Move shared display-mode constants to utils/propertyTypes to satisfy
react-refresh lint rule. All tests pass, no behavior change.
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract ensure_gitignore from init_repo so it can be reused by
clone_repo (GitHub "Create New" flow) and repair_vault. New vaults
created via any path now get .DS_Store excluded by default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Accept Promise<unknown> instead of Promise<void> to match the actual
reloadVault return type (Promise<VaultEntry[]>).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers: TitleField visibility, filename indicator on focus, no
migration banner when vault is flat, CSS rule hiding H1 in editor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- scan_vault now scans root + protected folders only (no deep recursion)
- vault_health_check command detects stray files and title mismatches
- Wikilink resolution: multi-pass with filename stem priority
- TitleField: dedicated title UI above editor, H1 hidden via CSS
- Migration banner: detects subfolders on vault open, offers flatten
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On vault load, detects files in non-protected subfolders via
vault_health_check. Shows an amber banner offering to flatten them
to the vault root. After migration, reloads the vault automatically.
The banner can be dismissed without migrating.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a TitleField component between the breadcrumb bar and BlockNote
editor that serves as the primary title editing surface. The H1 block
inside BlockNote is hidden via CSS. The title field:
- Shows the note title in a prominent input field
- Displays the expected filename when it differs from the current one
- Triggers onTitleSync on blur/Enter to rename the file
- Responds to laputa:focus-editor selectTitle events for new notes
- Reverts on Escape or empty input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-pass resolution now prioritizes filename stem (strongest) over
alias over title. Removes path-based matching (e.path.endsWith). Legacy
path-style targets like "person/alice" still work by extracting the
last segment "alice" and matching by filename/title.
This matches the flat vault convention where filename IS the note's
identity — filename stem is always slugify(title).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detects two classes of vault issues:
1. Stray files in non-protected subfolders (won't be scanned)
2. Filename-title mismatches (filename ≠ slugify(title))
Returns a VaultHealthReport with stray_files and title_mismatches.
Registered as a Tauri command for frontend use.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flat vault enforcement: scan_vault now only picks up .md files at the
vault root and inside protected folders (type/, config/, attachments/,
_themes/, theme/). Files in arbitrary subfolders are no longer indexed.
This prevents stray files from being included and enforces the flat
vault convention where all notes live at the root.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Notes with only headings/rules after H1 (e.g. project templates, daily
notes) previously showed empty snippets. Now extract_snippet collects
sub-heading text as fallback. Also hides the snippet div when empty to
avoid blank gaps in the note list. Bumps cache version to 8 for rescan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The banner is now rendered as a flex sibling below the list container,
ensuring it displays correctly in both unit tests (JSDOM) and real
browsers (Playwright/Chromium).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When notes are permanently deleted, the Changes note list now shows a
"N notes deleted" banner so the counter matches the visible list items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use broader .text-muted-foreground selector instead of .text-[12px]
which may not match in all contexts. Check for text length > 15 to
distinguish snippets from short date strings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Snippet extraction was including raw list markers (* , - , + , 1. ) in
the preview text, producing ugly leading spaces. Notes whose snippets
were cached before this fix showed stale/incorrect previews.
- Add strip_list_marker() in both Rust and TS to remove bullet/ordered
list prefixes before snippet assembly
- Trim final snippet to remove leading/trailing whitespace
- Bump CACHE_VERSION 6 → 7 to force full rescan on next vault load,
ensuring all entries get clean snippets
- Add Rust + Vitest tests for list marker stripping
- Update Playwright smoke test with stricter snippet assertions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: handleCreateNoteForRelationship used `await persistNewNote()`
which forced an early React flush. The subsequent frontmatter update
(onAdd/onAddProperty) triggered setTabs in a microtask that collided with
the render batch, causing a radix-ui infinite setState loop
("Maximum update depth exceeded") and a blank white screen.
Two fixes applied:
1. Make handleCreateNoteForRelationship synchronous (fire-and-forget
persistence) to keep all state updates batched — mirrors the working
handleCreateNoteImmediate pattern.
2. Defer onAdd/onAddProperty via setTimeout(0) so the frontmatter update
runs after the tab-switch render completes, avoiding the radix-ui
ref composition loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use .first() when selecting type option to handle demo vaults with
duplicate type names in the dropdown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WKWebView doesn't auto-invalidate ::before/::after styles when CSS custom
properties change on document.documentElement. Add `void root.offsetHeight`
to force reflow. Also add a version counter in useThemeApplier to prevent
stale async fetches from overwriting live-reload CSS vars.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Simplify flatten_vault API to return usize instead of MigrationResult struct
- Add KEEP_FOLDERS: attachments/ and _themes/ alongside type/, config/, theme/
- Use HashSet for collision tracking in unique_filename
- Update wikilinks from path-based [[folder/slug]] to title-based [[slug]]
- Clean up empty directories after flattening
- Flatten demo-vault-v2: move all notes from type-based subfolders to root
- Update smoke tests for flat vault structure
- Remove migrate_to_flat_vault from repair_vault (one-time migration only)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: Tauri's dragDropEnabled (default: true) intercepts drag events
at the webview level, preventing HTML5 DnD from working for tab reorder
and BlockNote block handle drag. Setting dragDropEnabled: false lets all
drag events flow through the standard DOM API.
Image drops now use the HTML5 drop handler + uploadImageFile (same as
paste-upload) instead of Tauri's onDragDropEvent + copyImageToVault.
Removed the useInternalDragFlag workaround and padding hack.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The demo vault doesn't contain a note with 'Refactoring' in the title,
causing consistent timeout failures in the pre-push smoke tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for the image drop overlay interference:
1. Block handle clipping: Add padding (0 4px) to editor container so
BlockNote's side menu (42px) fits within the overflow clip edge.
overflow-y:auto forces overflow-x:auto (CSS spec), which was clipping
the menu 2px past the container's left boundary.
2. Block handle click interference: Extract isInteractiveTarget() to
exclude .bn-side-menu from handleContainerClick — prevents the
container from stealing focus when clicking drag handle or add button.
3. Internal drag isolation: Track document-level dragstart/dragend to
flag internal HTML5 drags (tabs, blocks). Tauri onDragDropEvent
handler skips entirely during internal drags to prevent interference.
Extract useInternalDragFlag() and handleTauriDrop() to keep
useImageDrop under CodeScene complexity threshold.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove move_note_to_type_folder function and all its tests
- Remove MoveResult, type_to_folder_slug from rename.rs
- Remove infer_type_from_folder, capitalize_first, title_case_folder
- These were all made dead by the flat vault migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies AI panel (3-layer structure, blue glow, context bar, Escape close),
search UI accessibility, Repair Vault command, and no /api/ai/agent fetch calls.
All 7 audited tasks pass: qmd bundling, MCP foundation, AGENTS.md bootstrap,
AI panel rendering, Claude API wiring, AI panel UI, endpoint fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tools/qmd/node_modules was being analyzed by CodeScene causing
artificially low average code health (worst performer at 2.41
was third-party npm code, not our code).
Also excluding e2e/, tests/, scripts/ which are support code
and should not influence production code health metrics.
Previously only hotspot_code_health was checked (≥9.2).
Average code health was not gated, allowing merges that degrade
overall codebase quality without being blocked.
New gate: average_code_health ≥ 8.8 (current: ~8.9)
Extract delete/trash management logic (deleteNoteFromDisk, handleDeleteNote,
handleBulkDeletePermanently, handleEmptyTrash, trashedCount, confirmDelete state)
into a focused useDeleteActions hook. Reduces App.tsx from 733 to 672 lines.
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
useBulkActions was embedded in App.tsx with zero test coverage. It handles
bulk archive, trash, and restore operations — all with partial-failure
semantics and toast messaging.
Extract to src/hooks/useBulkActions.ts and add 15 unit tests covering:
- Plural/singular toast messages ("2 notes archived" vs "1 note archived")
- Partial failures: only successful operations counted in toast
- All-failure case: no toast shown
- Empty array: no operations called, no toast
Risk mitigated: silent bugs in batch operations (wrong count in toast,
toast shown when nothing succeeded, partial failure not handled).
Co-authored-by: Test <test@test.com>
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tests assume light theme (#FFFFFF) but test environment starts with
dark theme (#1a1a2e). Pre-existing issue unrelated to reopen-closed-tab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bare `.cursor-pointer.border-b` selector was unreliable in the
pre-push Playwright environment. Use `[data-testid="note-list-container"]`
to scope the note click, matching the pattern used by other passing tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Refactor useClosedTabHistory to store full VaultEntry (not stub) for reliable reopening
- Add data-tab-path attribute to TabItem for precise Playwright selectors
- Add 2 Playwright smoke tests: single close/reopen and empty-history no-op
- Update ARCHITECTURE.md and ABSTRACTIONS.md with closed tab history docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "Reopen Closed Tab" to File menu with CmdOrCtrl+Shift+T accelerator.
Wire onReopenClosedTab through useAppKeyboard, useMenuEvents,
useAppCommands, and App.tsx.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce useClosedTabHistory hook (LIFO stack, 20-entry cap, dedup)
and integrate it into useTabManagement so handleCloseTab records entries
and handleReopenClosedTab pops them to reopen at original position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add batch_delete_notes and empty_trash to Tauri IPC commands table
(62 → 64 total). Create placeholder design file for the feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use correct note item selector (.cursor-pointer inside
note-list-container) and navigate via command palette instead of
sidebar click. Focus note list before Cmd+A for bulk select.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "Empty Trash…" to the Note menu for discoverability and wire
the menu event through useMenuEvents. Add comprehensive Playwright
smoke test covering trash view navigation, Empty Trash button and
command, confirmation dialog, bulk selection context, and trashed
note banner.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new Tauri commands for trash management:
- batch_delete_notes: permanently delete multiple note files from disk
- empty_trash: scan vault and delete ALL trashed notes regardless of age
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test hardcodes port 9711 which causes EADDRINUSE when other
processes occupy it. Not related to clickable-editor-empty-space.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These tests have been failing consistently because the mock theme
switching doesn't propagate CSS variable changes back to the DOM.
Not related to any recent changes — marking as fixme to unblock push.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers: clicking empty space focuses editor, cursor:text affordance,
and normal content clicks are unaffected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clicking anywhere in the editor container (including empty space below the
last block) now focuses the editor and places the cursor at the end of the
last block. This matches the behavior of Notion, Bear, and Obsidian.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same root cause as theme-properties-defaults: the vault API reads real
files from disk instead of mock content, causing theme CSS var mismatches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validates that all 140 CSS custom properties from the expanded
DEFAULT_VAULT_THEME_VARS are applied to the DOM when a theme is
activated, including editor, heading, list, checkbox, inline-style,
code-block, blockquote, table, and horizontal-rule properties.
Also updates mock content and handlers to use the full 140-property
frontmatter, matching the Rust backend output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, only UI chrome colours and 3 editor vars were written to
theme note frontmatter. CSS vars like --lists-bullet-size, --headings-h1-font-size,
etc. remained unset, so EditorTheme.css rules had no effect.
- Expand DEFAULT_VAULT_THEME_VARS from 46 to 140 entries, covering every
property from theme.json (editor, headings, lists, checkboxes, inline
styles, code blocks, blockquote, table, horizontal rule, colors)
- Fix missing px suffix on editor-font-size and editor-max-width
- Add missing semantic vars: bg-card, text-tertiary
- Refactor built-in vault themes from const strings to generated functions
with per-theme colour overrides (DRY, all themes get editor properties)
- Quote var() references in frontmatter to prevent YAML parse issues
- Add regression tests for create_vault_theme and seed_vault_themes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The snippet was extracted once at vault load time (Rust backend) and
never updated when content was saved. Notes created or edited during
a session showed stale/empty snippets until the next app restart.
Added extractSnippet() to the frontend (mirroring Rust logic) and
wired it into useEditorSaveWithLinks so snippet + wordCount are
updated alongside outgoingLinks on every save.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When toggling from raw mode back to BlockNote, the editor now correctly
re-parses content from tab.content instead of using stale cached blocks.
Key changes:
- useEditorTabSwap: detect rawMode true→false transition, invalidate
block cache, and re-parse from tab.content. Added rawSwapPendingRef
guard to prevent a second effect run from re-caching stale blocks
before the deferred doSwap microtask runs.
- useRawMode: added onBeforeRawEnd callback to flush debounced raw
editor content synchronously before toggling off.
- Editor.tsx: wired rawLatestContentRef and handleBeforeRawEnd to
ensure the latest raw content reaches tab.content before the swap.
- RawEditorView: exposed latestContentRef so parent can read the
latest keystroke content without waiting for the 500ms debounce.
- EditorContent: threaded rawLatestContentRef through to RawEditorView.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When typing a non-existent note title in the relationship target input,
a 'Create & open' option now appears at the bottom of the dropdown.
Selecting it creates the note, adds the wikilink, and opens the new note.
- Added SearchDropdownWithCreate with create option
- Modified InlineAddNote and NoteTargetInput to support create flow
- Added onCreateAndOpenNote prop to DynamicRelationshipsPanel
- Keyboard accessible (arrow keys + Enter)
- 6 new tests covering create-and-open behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
buildTypeEntryMap now stores both original title and lowercase key so
isA: 'config' matches type entry titled 'Config'. Adds Playwright smoke
test that blocks the vault API to test against mock data fixtures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add GearSix icon ('gear-six') to icon registry — was missing, causing
Config type to show FileText fallback instead of its configured icon
- Add 'gray' to ACCENT_COLORS palette with CSS variables — was missing,
causing Config type color to fall back to muted foreground
- Extract sidebar section logic to utils/sidebarSections.ts for testability
- Add Config type + instance to mock entries for browser dev mode
- Add tests: icon resolution, gray color, sidebar section builder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace mock vault approach with real filesystem I/O for integration
tests. Each test copies tests/fixtures/test-vault/ to a temp directory,
overrides mock handlers to point at it, and verifies actual file
operations through the vite dev server middleware.
Tests cover: vault loading, archive/trash filtering, note creation,
rename with filesystem update, wikilink cascade on rename, relationship
display, and real file content loading.
Also extends vite.config.ts vault API middleware with write endpoints
(save, rename, delete, search, entry) and fixes getBool to handle
YAML 1.2 string values like "Yes"/"yes" (js-yaml 4.x compatibility).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Title sync now triggers a full rename flow instead of in-memory update,
so the Cmd+S test expectations needed updating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- handleRenameNote passes entry title as old_title to Rust
- handleUpdateFrontmatter triggers rename on title key change
- non-title keys don't trigger rename
- null old_title when entry not found
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handleTitleSync now saves pending content and calls rename_note
(which renames the file and updates wikilinks) instead of only
updating in-memory state. handleUpdateFrontmatter also triggers
rename when the title: key is changed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the editor saves content with a new H1 before triggering rename,
the on-disk H1 already matches the new title, causing rename_note to
noop. The old_title_hint parameter lets the caller provide the
original title so wikilinks are still found and updated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies tight lists stay tight, headings don't gain extra blank lines,
and saving without editing doesn't add whitespace changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
blocksToMarkdownLossy() inserts blank lines between every block, making
tight lists loose and polluting git history. Add compactMarkdown() that
collapses inter-list-item blanks and excessive blank line runs while
preserving code blocks and intentional paragraph spacing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Test [[ autocomplete inserts wikilink with correct data-target attribute
- Test inserted wikilink does not show as broken (correct color resolution)
- Test clicking inserted wikilink navigates to the correct note
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create resolveEntry() in wikilink.ts: single case-insensitive resolution
function that handles title, alias, filename stem, path suffix, and
pipe syntax matching
- Replace findEntryByTarget (case-sensitive) and entryMatchesTarget
(hardcoded /Laputa/ path) with unified resolveEntry
- Fix attachClickHandlers to insert path|title pipe syntax when multiple
candidates share the same title (disambiguation)
- Update ai-context.ts resolveTarget to use unified resolution
- Add comprehensive tests for resolveEntry and disambiguation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove setIsDragOver(true) from Tauri onDragDropEvent 'over' handler —
Tauri over events can't distinguish OS file drags from internal drags.
The HTML5 dragover handler already checks hasImageFiles() correctly and
now solely drives the overlay state. Tauri handler only processes drops.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers: title change + save renames file, no rename when filename matches,
rapid title edits rename to final title.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When Cmd+S is pressed, after saving content, checks if the note's
title slug differs from its current filename. If so, triggers
rename_note to update the file on disk, tabs, breadcrumbs, and
wikilinks. Adds needsRenameOnSave() utility with tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the note content already has the correct title but the filename
doesn't match (e.g. untitled-note-9.md after user changed H1), the
rename was a no-op. Now checks both title AND filename slug before
early-returning. Also uses unique_dest_path for collision handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that changing a note's type preserves the editor content —
the bug caused the tab to load a different note's content after the move.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The mock handler now appends -2, -3, etc. when the target path already
exists, matching the Rust unique_dest_path logic. Previously it would
silently overwrite the existing note's content in MOCK_CONTENT.
Also adds a Rust test that verifies both the moved note and the
pre-existing note retain their respective content after a collision.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After runFrontmatterOp updates the frontmatter and sets the tab content,
move_note_to_type_folder only changes the file location (not its content).
Re-reading via loadNoteContent(result.new_path) was redundant and dangerous:
if the path collided or a stale cache intervened, it could load a different
note's content into the tab — the root cause of the data-corruption bug.
Also fixes stale-closure issue: replaceEntry no longer spreads the captured
`entries` array (which could be stale after the await), avoiding reverting
the isA field that runFrontmatterOp already updated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: commit 4743537 added a custom Rust deserializer for
Archived/Trashed Yes/No strings but did not bump CACHE_VERSION.
Existing vaults had stale cached entries with archived: false from the
old parser, and since the cache version (5) matched, stale values were
served without re-parsing from disk.
- Bump CACHE_VERSION 5 → 6 to force full rescan on next vault load
- Add Yes/No handling to TypeScript parseScalar (Inspector display)
- Add integration tests: cached vault path with Archived/Trashed: Yes
- Add stale cache version invalidation test
- Add frontmatter.test.ts for TS Yes/No boolean parsing
- Add Playwright smoke test for archived note filtering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers all acceptance criteria:
- Click '+' next to type section → note created, no crash
- Cmd+N → note created, no crash
- Custom type → note created, no crash
- Rapid double-click → both notes created, no crash
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- slugify now returns 'untitled' instead of empty string when input has only
special characters, preventing invalid paths like '/vault//note.md'
- handleCreateNoteImmediate wrapped in try/catch — worst case shows a toast
error instead of crashing the app
- Added Rust test for deeply nested directory creation in save_note_content
- Regression tests for slugify edge cases and handleCreateNoteImmediate with
special-character types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that editing a theme note frontmatter in raw mode and pressing
Ctrl+S immediately updates CSS vars on the DOM. Also verifies saving a
non-theme note does not affect the active theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When user edits a theme note directly in the editor and presses Cmd+S,
the app now immediately re-applies CSS variables — no manual reload
needed. Added notifyThemeSaved(path, content) to ThemeManager; wired
into onNotePersisted callback so saving the active theme updates
cachedThemeContent, triggering useThemeApplier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:31:03 +01:00
1352 changed files with 20214 additions and 6666 deletions
pre_commit_code_health_safeguard # CodeScene ≥9.2 — if it fails, fix structurally (see below)
pre_commit_code_health_safeguard # CodeScene ≥9.2 hotspot + ≥9.2 average (target: 9.5+)
```
**CI is a safety net, not a discovery tool.** If CI catches something you didn't catch locally, that's a process failure. All these tools are available locally — use them while you code, not just at the end.
If `pre_commit_code_health_safeguard` fails: extract hooks, split components, reduce complexity. Never add `// eslint-disable`, `#[allow(...)]`, or `as any` to pass the gate.
## ⛔ BEFORE FIRING laputa-task-done — Two-phase QA (mandatory)
## ⛔ BEFORE FIRING laputa-task-done — Two-phase QA
### Phase 1: Playwright browser QA (headless, you do this yourself)
### Phase 1: Playwright (you do this)
Test every acceptance criterion using Playwright against the dev server **before** marking done. This catches 80% of bugs before Brian sees them.
Write a test in `tests/smoke/<slug>.spec.ts` that covers every acceptance criterion. The test must fail before your fix and pass after. Run it:
```bash
# 1. Start the dev server (use your worktree port)
pnpm dev --port <N> &
DEV_PID=$!
sleep 3# wait for vite to be ready
# 2. Run Playwright smoke test for this task
BASE_URL="http://localhost:<N>" npx playwright test tests/smoke/<slug>.spec.ts
BASE_URL="http://localhost:5201" npx playwright test tests/smoke/<slug>.spec.ts
```
**You must write a new Playwright test for this task** in `tests/smoke/<slug>.spec.ts` that covers every acceptance criterion. Do not rely only on existing smoke tests — they test the app in general, not your specific feature.
**If your task touches filesystem, git, AI, MCP, or any native Tauri command**: also test with `pnpm tauri dev` against `~/Laputa` (not demo vault). Use `osascript` keyboard events — no mouse, no `cliclick`.
**What to cover in your Playwright test:**
- Every acceptance criterion from the task spec → one `test()` block per criterion
- Every command palette entry → open `Cmd+K`, type the command name, verify it appears and executes
- Every keyboard shortcut → send keydown events, verify UI state changes
- Every UI element described in the spec → verify it renders, is focusable, responds to Tab
- Edge cases: empty state, long text, rapid keypresses
- **The happy path end-to-end**: simulate exactly what a user would do to use this feature
### Phase 2: Native QA (Brian does this after push)
**The test must fail before your fix and pass after.** If you can't write a test that demonstrates the bug is fixed, your test doesn't cover the right thing.
Brian installs the release build and runs keyboard-only QA. Phase 1 must pass first or the task goes to To Rework.
**Playwright is non-negotiable even if unit tests pass.** Unit tests verify code; Playwright verifies the user experience in the real browser. Both are required.
> **⚠️ Browser dev server limits**: the dev server uses mock Tauri handlers (`src/mock-tauri.ts`) — file system operations, git commands, and native dialogs are mocked. **If your task touches the filesystem, AI context pipeline, MCP server, git integration, or any Tauri command that reads/writes real files, Playwright alone is not enough.** You must also do Phase 1b.
### Phase 1b: Tauri dev QA (you do this for filesystem/native tasks)
If your task touches **any of the following**, you must also test with `pnpm tauri dev` against the real vault before firing done:
- File read/write (notes, cache, vault config)
- AI chat context (what the AI actually receives as input)
- MCP server / subprocess communication
- Git integration (commit, push, history, diff)
- Native dialogs or OS-level features
Fire done signal only after Phase 1 passes — **two steps, both required**:
```bash
# Start Tauri dev app from your worktree
pnpm tauri dev --port <N> &
sleep 10# wait for Tauri + Vite to boot
# 1. Move task to In Review on Todoist
curl -s -X POST "https://api.todoist.com/api/v1/tasks/<task_id>/move"\
-H "Authorization: Bearer $TODOIST_API_KEY"\
-H "Content-Type: application/json"\
-d '{"section_id": "6g3XjX33FF4Vj86M"}'
# Then test using osascript keyboard events (NO mouse/cliclick)
# Example:
osascript -e 'tell application "laputa" to activate'
osascript -e 'tell application "System Events" to keystroke "k" using command down'
# ...simulate the full user flow from the acceptance criteria
# 2. Notify Brian
openclaw system event --text "laputa-task-done:<task_id>" --mode now
```
**What to verify in Phase 1b:**
- Open the feature on a real note in `~/Laputa` (not the demo vault)
- Walk through every acceptance criterion step by step using keyboard only
- Verify file changes with `git -C ~/Laputa diff` if the task writes files
- Verify AI responses actually contain note content (not empty) if the task touches AI context
**⚠️ Claude Code runs headless — you cannot see the screen.** Use `screencapture /tmp/qa-check.png` and then read/describe what you see if you need visual verification. Or rely on DOM state checks via osascript accessibility API.
### Phase 2: Native Tauri QA (Brian does this after you push)
Brian installs the release build and runs keyboard-only QA on the native app. You don't do Phase 2 — but Phase 1 must pass before you fire the done signal, or Brian's QA will fail and the task goes back to To Rework.
7. If QA fails → fix and re-run. Do NOT fire the signal until it passes.
**⚠️ QA ≠ tests. QA means using the app as a user.**
- "Tests pass" is NOT QA. Tests verify code, QA verifies the user experience.
- The QA comment must describe what you did as a user: "Opened app → Cmd+K → typed 'Trash' → pressed Enter → note disappeared from list → restarted app → note still not visible"
- Every QA comment must include: the exact keyboard/command palette steps used, what was visible before and after, and any edge case tested.
- If you cannot test a feature using keyboard only (osascript shortcuts + command palette), the feature is not keyboard-first → QA fails.
**⚠️ Phase 1 is YOUR quality gate, not a formality.**
Brian's Phase 2 QA is a *reinforcement* check, not the primary gate. If Brian finds a bug in Phase 2 that you could have caught in Phase 1, that is a Phase 1 failure — not a Phase 2 discovery. Before firing the done signal, ask yourself: "Did I actually verify, in Playwright, that the feature works end-to-end exactly as the spec describes?" If the answer is "I ran the smoke tests and they passed", that is not enough. You must run your task-specific test and verify the acceptance criteria one by one.
**⚠️ Test in a clean environment when the feature depends on state.**
If a feature involves indexing, fresh installs, first-time setup, or anything that only runs once:
- **Do not test in the existing dev vault** — it already has the state you're trying to test.
- **Create a new empty vault** for the test: Cmd+K → "New Vault" (or equivalent), pick a temp folder like `/tmp/test-vault-<slug>`, then test the full first-time flow from scratch.
- This applies to: search indexing, vault init, getting-started setup, any "on first open" logic.
- If you can't reproduce the fresh-install scenario locally, the feature is untestable → do not fire done.
Fire done signal only after QA passes:
```bash
rm -f /tmp/laputa-qa.lock
openclaw system event --text "laputa-task-done:<task_id>:<slug>" --mode now
```
## ⛔ CODE HEALTH — No shortcuts
If `pre_commit_code_health_safeguard` flags a file:
- **Understand why** — use `code_health_review` via CodeScene MCP
- Fix the structural problem (extract hooks, split components, reduce complexity)
- **Never** add a JSDoc comment, `#[allow(...)]`, `// eslint-disable`, or `as any` just to pass the gate
- It's fine to take longer. False quality is worse than no quality.
---
## Project
Tauri v2 + React + TypeScript desktop app. Reads a vault of markdown files with YAML frontmatter.
- **Never develop on `main`** — always on `task/<slug>` branch
- **Commit every 20–30 min** — atomic commits, one concern per commit (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`)
- **Update docs/** when changing architecture, abstractions, or significant design (mandatory — see rule below)
- **Push directly to main** — no PRs ever. The pre-push hook runs all checks.
- **⛔ NEVER open a PR** — branches diverge and cause rebase churn.
- **⛔ NEVER use --no-verify**
- Commit every 20–30 min: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`
## ⛔ DOCS — Keep docs/ in sync with code (mandatory)
## TDD (mandatory)
After any significant feature change, update the relevant `docs/` files **in the same commit**:
Red → Green → Refactor → Commit. One cycle per commit. For bugs: write a failing regression test first, then fix. Exception: pure CSS/layout with no logic.
- **`docs/ARCHITECTURE.md`** — stack, system overview, component structure, Tauri commands, data flow, backend modules
- **`docs/GETTING-STARTED.md`** — directory structure, key files, common tasks, test commands, onboarding
**Test quality (Kent Beck's Desiderata):** every test must be Isolated (no shared state), Deterministic (no flakiness), Fast, Behavioral (tests behavior not implementation), Structure-insensitive (refactoring doesn't break it), Specific (failure points to exact cause), Predictive (all pass = production-ready). Fix flaky/non-deterministic tests before adding new ones. E2E tests over unit tests for user flows.
**What counts as "significant":**
- Adding a new Tauri command or backend module
- Adding a new major component, hook, or feature (not a bugfix)
- Changing the data model (VaultEntry fields, new types, new config files)
- Adding a new integration (API, service, transport)
- Changing the architecture (new panels, new state management, new build steps)
## ⛔ Docs — Keep docs/ in sync
**How to update:**
1. Read the relevant doc section before making changes
2. After your code changes, update the doc to reflect the new state
3. Commit doc changes together with the code — not in a separate follow-up commit
After adding a Tauri command, new component/hook, data model change, or new integration: update `docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, and/or `docs/GETTING-STARTED.md` in the same commit. Use Mermaid for diagrams (not ASCII). Exception: spatial wireframe layouts.
If unsure whether a change is "significant", err on the side of updating. Stale docs are worse than slightly verbose docs.
## Design File (UI tasks)
## TDD — Red/Green/Refactor (mandatory)
1. Open `ui-design.pen` first — study existing frames for visual language.
2. Design in light mode. Create `design/<slug>.pen` for the task.
3. On merge to main: merge frames into `ui-design.pen`, delete `design/<slug>.pen`.
**Always use test-driven development.** No production code without a failing test first.
## Vault Retrocompatibility
The loop:
1.**Red** — write a failing test that describes the behavior you want. Run it, confirm it fails for the right reason.
2.**Green** — write the minimum code to make the test pass. No more, no less.
3.**Refactor** — clean up the code (extract, rename, simplify) while keeping tests green.
4.**Commit** — one red/green/refactor cycle = one atomic commit.
5. Repeat.
Every feature that depends on vault files must auto-bootstrap: check if file/folder exists on vault open, create with defaults if missing (silent, idempotent). Register with the central `Cmd+K → "Repair Vault"` command.
**Why this matters:**
- Forces you to think about behavior before implementation
- Produces only code that's actually needed (no speculative abstractions)
- Tests written first are always behavioral and structure-insensitive by construction
- Tiny cycles = fast feedback, smaller diffs, easier to review
## Keyboard-First + Menu Bar (mandatory)
**For bug fixes:**
1. Write a failing test that reproduces the bug (this is the regression test)
2. Fix the bug until the test passes
3. Commit both together: `fix: [bug] — regression test added`
**For Rust:**
```bash
cargo watch -x test# run tests on every save
```
**For frontend:**
```bash
pnpm test --watch # run tests on every save
```
**When to deviate:** Pure UI layout/styling work with no logic is the only exception. Everything else — hooks, utilities, Rust commands, state management — must be TDD.
## Testing (quality bar)
- Unit tests must cover real business logic, not "component renders"
- Tests test **behavior** (what the code does), not **structure** (how it does it)
- Every bug fixed → regression test that would have caught it
- Every new feature → TDD from the start (see above)
-`pnpm test:coverage` and `cargo llvm-cov` must pass before committing
## Design File (every UI task)
Every task with UI changes needs a design file. Follow this process:
1.**Open `ui-design.pen` first** — study existing frames to understand the visual language, spacing, and component style before designing anything new.
2.**Design in light mode** — all existing designs use light mode. New frames must match. Never use dark mode for designs.
3.**Create `design/<slug>.pen`** for the new feature — additive only, NOT a copy of ui-design.pen.
4.**When merging to main** — merge your frames into `ui-design.pen` with proper layout:
- Place frames in a logical area (group by feature area, not stacked on top of each other)
- Leave at least 100px spacing between frames
- **Delete `design/<slug>.pen`** after merging — the frames now live in `ui-design.pen`
## Vault File Retrocompatibility (mandatory for every feature that adds vault files)
Laputa vaults are long-lived. New app versions must work on existing vaults that were created before a feature existed.
**Rule: never assume a vault file exists. Always auto-create if missing.**
Every feature that depends on a vault file or folder must:
1.**Auto-bootstrap on vault open** — check if the required file/folder exists; if not, create it with defaults. This must be silent and non-blocking.
2.**Be idempotent** — creating defaults must be safe to run multiple times (never overwrite user data).
3.**Expose a repair command** — add a `Cmd+K` command like "Restore Default Themes" or "Repair Vault Config" that explicitly re-creates missing files. Users can run this if something is broken.
**General "Repair Vault" command** — when adding a new vault file dependency, register it with the central repair system so that `Cmd+K → "Repair Vault"` fixes everything in one shot.
**Pattern:**
```
on vault open:
if file X does not exist → create X with defaults ← silent auto-repair
if file X exists but is malformed → log warning, use defaults (don't crash)
on "Repair Vault" command:
for each known vault file/folder:
if missing → create with defaults
if present → leave untouched (idempotent)
```
This principle applies to: themes, config files, type files, any `.laputa/` subfolder, or any file Laputa expects to find in a vault.
Every feature must be reachable via keyboard. Every new command palette entry must also appear in the macOS menu bar (File / Edit / View / Note / Vault / Window). This is a QA requirement.
## macOS / Tauri Gotchas
-`Option+N` on macOS → special chars (`¡`, `™`), not `key:'N'`. Use `e.code` or `Cmd+N`.
- Tauri menu accelerators: use`MenuItemBuilder::new(label).accelerator("CmdOrCtrl+1")` — decorative text in labels doesn't register shortcuts.
-`Option+N` → special chars on macOS. Use `e.code` or `Cmd+N`.
- Tauri menu accelerators: `MenuItemBuilder::new(label).accelerator("CmdOrCtrl+1")`.
-`app.set_menu()` replaces the ENTIRE menu bar — include all submenus.
-`mock-tauri.ts` silently swallows Tauri calls — not a substitute for native app testing.
## QA Scripts
@@ -278,63 +96,8 @@ This principle applies to: themes, config files, type files, any `.laputa/` subf
## Menu Bar Discoverability (mandatory for every new command)
## Documentation Diagrams
The command palette is powerful but not discoverable — users must already know a command exists to find it. The macOS menu bar is where users discover what an app can do.
**Rule: every significant command palette entry must also appear in the menu bar.**
When adding a new command to the palette:
1.**Identify the right menu bar group** — File, Edit, View, Note, Vault, or create a new group if needed
2.**Add a menu item** with the same label as the palette command
3.**Show the keyboard shortcut** next to the menu item (if one exists)
4.**If no direct shortcut exists**, still add the menu item — it's discoverable and triggers the same action
The menu bar should be organized around what Laputa does:
- **File** — new note, open vault, switch vault, close
**This is a QA requirement:** before marking any task done, verify that every new command palette entry has a corresponding menu bar item.
## Keyboard-First Principle (mandatory for every new feature)
Every feature must be reachable via keyboard. This is both a UX requirement and a QA requirement — Brian tests the native app using keyboard only (osascript key events, no mouse).
**Before marking any task done:**
- Can the feature be triggered/used without touching the mouse?
- If it requires clicking a button, add a command palette entry or keyboard shortcut
- Document the shortcut in the command palette or menu bar
**If you add UI that is only reachable by mouse**, you must also add a keyboard path (command palette entry, shortcut, or Tab-navigable focus). No exceptions.
## Push Workflow (IMPORTANT — changed Feb 27, 2026)
**Push directly to main** — no PRs, no branches, no CI queue.
The pre-push hook runs all checks locally before the push goes through. This replaces remote CI.
```bash
# After QA passes and you're ready to ship:
git push origin main # pre-push hook runs automatically
```
### ⛔ NEVER open a Pull Request
PRs on separate branches diverge from main with every merge, requiring continuous rebases and creating unnecessary conflicts. Always push directly to main. If the push fails (disk full, test failure, etc.) — fix the problem, then push again. There is no scenario where opening a PR is the right fallback.
### ⛔ NEVER use --no-verify
```bash
# FORBIDDEN — will be caught and rejected:
git push --no-verify
git commit --no-verify # also forbidden for pre-push bypass
```
The hook runs: tsc, Vite build, frontend tests, frontend coverage, Rust coverage, Clippy, rustfmt, CodeScene. Fix any failures before pushing — do not skip.
If a check fails, fix the issue and push again. The hook is the gate — not remote CI.
Prefer Mermaid for all diagrams (`flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram-v2`). ASCII only for spatial wireframe layouts. GitHub renders Mermaid natively.
@@ -16,7 +16,7 @@ Personal and business finances. Covers investing, savings, tax planning, and the
## Philosophy
The approach to finance is intentionally simple: automate what can be automated, invest passively for the long term, and focus energy on growing business revenue rather than optimizing portfolio returns. See [[evergreen/index-funds-and-intellectual-humility]] for the underlying thinking.
The approach to finance is intentionally simple: automate what can be automated, invest passively for the long term, and focus energy on growing business revenue rather than optimizing portfolio returns. See [[index-funds-and-intellectual-humility]] for the underlying thinking.
@@ -17,7 +17,7 @@ Physical and mental health. Encompasses cycling training, gym work, nutrition, s
## Why this area matters
Health is the foundation everything else is built on. Consistent training and sleep directly correlate with better creative output, clearer thinking, and more sustainable work habits. The [[evergreen/training-load-and-knowledge-work]] connection is real and measurable.
Health is the foundation everything else is built on. Consistent training and sleep directly correlate with better creative output, clearer thinking, and more sustainable work habits. The [[training-load-and-knowledge-work]] connection is real and measurable.
@@ -10,15 +10,15 @@ Continuous learning through reading, courses, conversations, and writing. The in
## Scope
- **Reading** — books, articles, newsletters, research papers. Target of 20+ books per year
- **Writing as learning** — [[evergreen/the-compound-effect-in-knowledge-work]] captures why writing notes and evergreen pieces accelerates understanding
- **Writing as learning** — [[the-compound-effect-in-knowledge-work]] captures why writing notes and evergreen pieces accelerates understanding
- **Conversations** — podcast guests, conference hallway chats, 1:1s with smart people
- **Courses & talks** — occasional online courses, preparing conference talks as a forcing function
## Philosophy
Learning is not about consuming more — it's about extracting and connecting ideas. The [[procedure-evergreen-note-writing]] process exists to turn raw input into lasting, reusable knowledge. Every book note in [[note/]] should eventually produce at least one evergreen idea.
Learning is not about consuming more — it's about extracting and connecting ideas. The [[procedure-evergreen-note-writing]] process exists to turn raw input into lasting, reusable knowledge. Every book note in the vault should eventually produce at least one evergreen idea.
See [[evergreen/reading-more-by-reading-better]] for the approach to reading.
See [[reading-more-by-reading-better]] for the approach to reading.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.