Commit Graph

240 Commits

Author SHA1 Message Date
Luca Rossi
5ebcf41d47 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 10:50:36 +00:00
lucaronin
efd79e9a3d fix: resolve all ESLint errors (lint now exits 0)
- useAppKeyboard: move all logic inside useEffect, fixes refs-in-render
  and no-unused-expressions for view mode shortcut handler
- SettingsPanel: refactor to inner component to fix setState-in-effect;
  remove unused maskKey function
- NoteList: remove re-exports (consumers import from noteListHelpers directly);
  fix no-unused-expressions in toggleGroup; eslint-disable for Icon-in-render
- useNoteActions: eslint-disable tabsRef.current assignment (valid pattern)
- Test files: fix no-explicit-any in useKeyboardNavigation, useSettings,
  useVaultLoader, wikilinks tests; update NoteList.test import path
2026-02-23 08:53:43 +01:00
lucaronin
20ce35bb44 fix: use Cmd+1/2/3 shortcuts and register real Tauri menu accelerators
- Changed view mode shortcuts from Alt+1/2/3 to Cmd+1/2/3 (Alt+N produces
  special chars on macOS, e.g. Alt+1 → '¡')
- menu.rs: use MenuItemBuilder with .accelerator() instead of appending
  shortcut text to label (was purely decorative, not registered with OS)
- Shortcuts now: CmdOrCtrl+1 editor-only, +2 editor+list, +3 all panels
2026-02-23 08:36:07 +01:00
lucaronin
434395d602 test: add tests for useViewMode and useAppKeyboard view shortcuts
- useViewMode: default state, localStorage persistence, visibility flags
- useAppKeyboard: Option+1/2/3 view mode shortcuts, Cmd+key shortcuts
- Verifies Alt-only vs Cmd+Alt distinction for view mode keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:36:07 +01:00
lucaronin
486c0faa6f feat: collapsible sidebar and note list with ⌥1/2/3 shortcuts
- Add useViewMode hook: manages panel visibility state (editor-only, editor-list, all)
- Persist view mode preference in localStorage
- Add Option+1 (editor only), Option+2 (editor + note list), Option+3 (all panels)
- Add collapse button (CaretLeft icon) in sidebar title bar
- Extract useDialogs hook from App to reduce component size
- Extract SidebarTitleBar component to reduce Sidebar complexity
- Listen for Tauri menu events to support macOS View menu (wired next)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:35:51 +01:00
lucaronin
d88b16357b fix: remove unused DEFAULT_DIRECTIONS import from NoteList
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:35:31 +01:00
lucaronin
163a5606b6 test: add tests for sort direction behavior
- getSortComparator with explicit asc/desc direction for all sort modes
- Direction arrows visible in dropdown menu
- Clicking direction arrow reverses sort order in the list
- Direction persistence verified through sort behavior
- Direction icon shown on sort button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:35:30 +01:00
lucaronin
1230cc4e77 feat: add sort direction (asc/desc) to note list sorting
- Add SortDirection type and SortConfig interface to noteListHelpers
- getSortComparator now accepts optional direction parameter
- SortDropdown shows ↑/↓ arrow buttons per option in the menu
- Button label shows current direction arrow (ArrowUp/ArrowDown)
- Persistence updated to store {option, direction} with backward compat
  for old string-only preferences
- Default directions: modified/created=desc, title/status=asc
- Status sort tiebreaker always uses newest-first regardless of direction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:35:30 +01:00
Luca Rossi
53c881278e Merge pull request #13 from refactoringhq/task/vault-from-github
feat: vault from GitHub — create or clone a vault from a GitHub repo
2026-02-23 08:35:10 +01:00
lucaronin
077330ec18 fix: resolve pre-existing TS errors in wikilinks.test.ts
Added AnyBlock type alias to cast injectWikilinks() return type in tests.
The function returns unknown[] but tests access .content, .props, .text
properties — this was already broken before this branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:34:14 +01:00
lucaronin
5fa1ac09f5 fix: remove unused GithubRepo import in mock-tauri.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:34:14 +01:00
lucaronin
a4bdc359ce fix: sync in-memory entries after property/rename changes
Two propagation gaps fixed:

1. Property changes (type, status, color, etc.) now immediately sync to
   the entries state via frontmatterToEntryPatch(), so sidebar, note list,
   and relations panel reflect updates without restart.

2. After renaming a note, all other open tabs reload their content from
   disk, picking up updated wikilinks immediately.

Design decisions:
- Used key-mapping approach (frontmatterToEntryPatch) instead of a new
  parse_md_file Tauri command — simpler, works in both Tauri and mock
  modes, covers all VaultEntry fields.
- Converted useNoteActions to config object to avoid excess arguments.
- Extracted findWikilinkTarget, reloadTabsAfterRename, renameToastMessage
  as standalone functions to reduce hook complexity (cc 10→9).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:34:14 +01:00
lucaronin
17c145b865 test: add GitHubVaultModal component tests
12 tests covering:
- Renders nothing when closed
- Shows "connect account" prompt without token
- Opens settings from no-token state
- Shows clone/create tabs with token
- Loads and displays repo list from mock
- Filters repos by search term
- Selects repo and auto-fills clone path
- Disables clone button without selection
- Calls clone_repo and onVaultCloned on successful clone
- Create tab trigger is visible and clickable
- Shows error message on clone failure
- Shows Private/Public badges on repos

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:34:14 +01:00
lucaronin
861235981c feat: GitHub vault modal with clone/create flows
New GitHubVaultModal component with two tabs:
- Clone Existing: search/browse user's repos, select one, choose
  local path, clone via HTTPS+token
- Create New: enter repo name, toggle private/public, create via
  GitHub API then clone locally

Integration:
- "Connect GitHub repo" option in vault switcher menu
- GitHub token field added to Settings panel
- Cloned vaults auto-added to vault list and opened
- Mock handlers for github_list_repos, github_create_repo, clone_repo
- All existing tests updated for new github_token field (386 pass)

Product decision: GitHub token stored in settings rather than OAuth
flow — simpler first version, works with personal access tokens.
Users can upgrade to OAuth later.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:34:14 +01:00
lucaronin
6e4119c28b feat: add Rust backend for GitHub vault operations
New github.rs module with three commands:
- github_list_repos: lists user's repos via GitHub API (paginated)
- github_create_repo: creates a new repo with auto_init
- clone_repo: git clone with HTTPS+token auth, configures remote

Also extends Settings with github_token field (Rust + TypeScript),
and adds GithubRepo type to shared types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:34:14 +01:00
lucaronin
8f5bd68b2e fix: add missing useMemo import in useAppKeyboard.ts 2026-02-22 17:16:20 +01:00
lucaronin
f843b3b640 feat: settings panel — API key management + local config storage 2026-02-22 16:02:30 +01:00
lucaronin
659ca5990f fix: resolve all ESLint errors, enforce lint in CI 2026-02-22 14:31:02 +01:00
lucaronin
728a111f14 fix: resolve TypeScript build errors from any→unknown migration
- Editor.tsx: use targeted eslint-disable for BlockNote block arrays
  (PartialBlock generic type is extremely complex to reference)
- Editor.tsx: cast through unknown for tryParseMarkdownToBlocks result
  which may be sync or async
- useMcpBridge.ts: cast Promise resolve to (value: unknown) => void
- mock-tauri.ts: cast args.path to string for encodeURIComponent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:10:42 +01:00
lucaronin
8cb382e3a3 fix: resolve React hooks/compiler ESLint errors
- Wrap ref assignments in useEffect to fix refs-during-render in
  useTabManagement, useKeyboardNavigation, and Editor
- Rewrite useAppKeyboard to define keyMap inside useEffect, eliminating
  ref access during render
- Add eslint-disable for react-hooks/set-state-in-effect on legitimate
  dialog reset patterns (CommitDialog, CreateNoteDialog, CreateTypeDialog,
  QuickOpenPalette, AIChatPanel, useVaultLoader)
- Add eslint-disable for react-hooks/static-components on icon lookups
  (NoteItem, NoteList PinnedCard) — stateless icon components from a
  static map
- Add eslint-disable for react-hooks/purity on Date.now() in
  NoteItem TrashDateLine — intentionally memoized on trashedAt
- Remove unused `model` param from buildSystemPrompt (ai-chat.ts) and
  update callers
- Fix exhaustive-deps: suppress vault object dep in App.tsx git history
  effect (vault object is unstable, loadGitHistory is the real dep)
- Remove unused `modifiedFiles` from useNoteListData params and caller
- Add missing `ref` to Sidebar useOutsideClick deps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:06:51 +01:00
lucaronin
50a766504d test: settings panel and useSettings hook tests
- 14 SettingsPanel component tests: render, save, clear, keyboard
  shortcuts, backdrop close, field population, key trimming
- 5 useSettings hook tests: load, save, error handling
- Updated App.test.tsx mocks for settings commands and ai-chat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:42:20 +01:00
lucaronin
8b67ae53d4 feat: settings panel UI with API key management
- SettingsPanel modal component with masked key fields + reveal/clear
- useSettings hook reads/writes config via Tauri invoke
- Mock handlers for get_settings/save_settings in mock-tauri
- Settings type added to types.ts
- StatusBar gear icon opens settings (was disabled placeholder)
- Cmd+, keyboard shortcut opens settings
- Anthropic key synced to localStorage for AIChatPanel compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:38:18 +01:00
lucaronin
e5d96f97ec fix: suppress react-refresh/only-export-components for valid patterns
Co-located utility exports (constants, helper functions, variant
definitions) alongside their component files. These don't affect
Fast Refresh behavior in practice — targeted eslint-disable comments
explain each case:

- DynamicPropertiesPanel: containsWikilinks, RELATIONSHIP_KEYS
- NoteList: re-exported sort/filter utilities
- NoteItem: getTypeIcon utility
- SidebarParts: isSelectionActive utility
- TypeCustomizePopover: ICON_OPTIONS, resolveIcon
- badge/button/tabs: shadcn/ui variant export pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:11:38 +01:00
lucaronin
8e49af2c0e fix: replace no-explicit-any with proper types
- Test mocks: use { children?: React.ReactNode } and Record<string, unknown>
- Editor.tsx: use unknown[] for BlockNote block arrays
- wikilinks.ts: add BlockLike/InlineItem interfaces for block processing
- useMcpBridge.ts: use unknown for resolve/reject and Record<string, unknown>
- ai-chat.ts: use unknown catch + instanceof Error check
- mock-tauri.ts: type messages array, use Record<string, unknown> for args
  (keep one targeted eslint-disable for heterogeneous handler map)
- vite.config.ts: use http.IncomingMessage/ServerResponse, typed messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:10:26 +01:00
lucaronin
a77d5ff451 fix: simple ESLint errors — regex, unused vars, unused expressions
- eslint.config.js: ignore coverage/ directory (generated files)
- frontmatter.ts: no-regex-spaces — use {2} instead of literal spaces
- wikilinks.ts: no-useless-escape — remove unnecessary \[ escape
- ai-chat.ts: remove unused _model param from getContextLimit
- noteListHelpers.ts: remove unused _modifiedFiles param from filterEntries
- NoteList.tsx: replace ternary expression statement with if/else
- useTabManagement.ts: remove stale eslint-disable-next-line comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:08:16 +01:00
lucaronin
c55ffad4b7 feat: frontend test coverage 47% → 86% — 362 tests across 17 new test files 2026-02-22 13:02:56 +01:00
lucaronin
ee4faa1874 feat: border between macOS title bar and sidebar content 2026-02-22 12:31:51 +01:00
lucaronin
679e1e2372 feat: OTA updater plugin + GitHub Releases release pipeline 2026-02-22 12:31:42 +01:00
lucaronin
4d0840c6c6 feat: add border between macOS title bar and sidebar content
The macOS window frame (transparent title bar, 38px) and the sidebar
share the same background color (#F7F6F3), making them visually
indistinguishable. Replace the sidebar's paddingTop: 38 with a
dedicated spacer div that has a bottom border (1px solid var(--border)),
matching the border style used between all other panels. The spacer
also serves as a drag region for window movement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:11:39 +01:00
lucaronin
190b7d0075 feat: add OTA updater plugin with startup update check
- Install @tauri-apps/plugin-updater + plugin-process (npm + cargo)
- Configure tauri.conf.json: updater endpoint pointing to GitHub Releases,
  signing pubkey, createUpdaterArtifacts enabled
- Register updater + process plugins in Rust (desktop-only, #[cfg(desktop)])
- Add updater:default + process:default capabilities
- Add useUpdater hook: checks for updates 3s after startup, shows native
  confirm dialog, downloads+installs+relaunches on accept
- Silently catches errors so update failures never block the app
- Add comprehensive tests for the useUpdater hook (5 test cases)

Product decision: Using window.confirm() for the update dialog rather than
a custom React modal. This keeps the implementation simple and the native
dialog is appropriate for a system-level action like app restart. Can be
upgraded to a custom UI later if desired.

Private key generated at ~/.tauri/laputa.key — NOT committed.
For CI: set TAURI_SIGNING_PRIVATE_KEY + TAURI_SIGNING_PRIVATE_KEY_PASSWORD
as GitHub Secrets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:10:24 +01:00
lucaronin
f64d941b0e feat: rename note by double-clicking its tab — inline edit, updates filename + wiki-links
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:10:17 +01:00
lucaronin
e1cdd86ab2 test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
  add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
lucaronin
63c3bc3b1f test: add wikilinks.ts unit tests — preProcess, inject, splitFrontmatter, countWords
22 tests covering:
- preProcessWikilinks: single/multiple/pipe alias/nested wikilinks
- injectWikilinks: text expansion, nested children, no-content blocks
- splitFrontmatter: with/without frontmatter, unclosed frontmatter
- countWords: with frontmatter, markdown stripping, empty/whitespace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:29 +01:00
lucaronin
a36bcd6d43 test: add unit tests for hooks — useNoteActions, useEntryActions, useTabManagement, useVaultLoader, useKeyboardNavigation, mockFrontmatterHelpers
89 tests covering:
- useNoteActions: slugify, buildNewEntry, entryMatchesTarget, buildNoteContent,
  resolveNewNote/Type, createNote, createType, navigateWikilink, frontmatter ops
- useEntryActions: trash/restore, archive/unarchive, customizeType, reorderSections
- useTabManagement: tab selection, close, switch, reorder, replace, localStorage
- useVaultLoader: vault loading, modified files, addEntry, updateContent,
  updateEntry, git history, diff, commitAndPush
- useKeyboardNavigation: Cmd+Shift+Arrow tab switching, Cmd+Alt+Arrow note nav
- mockFrontmatterHelpers: updateMockFrontmatter, deleteMockFrontmatterProperty

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:19 +01:00
lucaronin
c67ba46062 refactor: export pure functions from useNoteActions + update coverage config
Export buildNewEntry, slugify, entryMatchesTarget, buildNoteContent,
resolveNewNote, resolveNewType as public functions for direct unit testing.

Add coverage exclusions for untestable files: shadcn/ui primitives,
AI modules (useAIChat, useMcpBridge, ai-chat, AIChatPanel), and types.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:07 +01:00
lucaronin
08bcab17c0 feat: leaner note creation — immediate Untitled note, no dialog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:38:03 +01:00
lucaronin
5bbd1469e2 fix: resolve TypeScript build errors in refactored code and test mocks
- Remove unused cn import from NoteList.tsx
- Fix SidebarSelection type narrowing in NoteList entity view
- Fix SortableSection onToggle type in Sidebar Omit type
- Fix TypeRow isA prop to accept null
- Align useEntryActions handleUpdateFrontmatter value type
- Add missing trashed/trashedAt/order to test mock VaultEntry objects

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:00:07 +01:00
lucaronin
65d1c753ef refactor: improve code health across 6 more files
- DynamicPropertiesPanel.tsx: extract PropertyValueCell, TypeRow, AddPropertyForm (9.06 → 9.68)
- useKeyboardNavigation.ts: extract classifyShortcut and useLatestRef (9.09 → 9.63)
- frontmatter.ts: extract parseScalar, parseInlineArray, isBlockScalar helpers (9.24 → 9.6)
- StatusBar.tsx: extract VaultMenu and VaultMenuItem components (9.23 → 10.0)
- useVaultLoader.ts: extract tauriCall helper and loadVaultData (9.35 → 10.0)
- useNoteActions.ts: extract resolveNewNote, resolveNewType, executeFrontmatterOp (9.22 → 9.38)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:55:45 +01:00
lucaronin
54fe2793fc refactor: reduce complexity in App, DiffView, Editor, InspectorPanels, NoteList
- App.tsx: extract useEntryActions hook and useLayoutPanels hook (8.92 → 9.28)
- DiffView.tsx: extract classifyDiffLine and DiffLine component (9.09 → 10.0)
- Editor.tsx: extract findEntryByTarget and lookupColorForEntry (9.09 → 10.0)
- InspectorPanels.tsx: extract entryStatusTitle and StatusSuffix (8.87 → 10.0)
- NoteList.tsx: extract EntityView, ListView, useNoteListData (8.79 → 9.68)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:48:13 +01:00
lucaronin
178d0784d1 refactor: decompose TabBar and Sidebar for reduced complexity
TabBar (8.50 → 9.38):
- Extract useTabDrag hook for drag state management
- Extract computeDropTarget/computeInsertIndex helpers
- Extract TabItem, DropIndicator, TabBarActions sub-components
- TabBar main function reduced from cc=23/176 LoC to ~25 LoC

Sidebar (8.57 → 9.38, SidebarParts 10.0):
- Create SidebarParts.tsx with NavItem, SectionContent, VisibilityPopover
- Extract useSidebarSections hook for section computation
- Extract useEntryCounts hook consolidating 3 useMemos into 1
- Extract useOutsideClick hook (eliminates 3 duplicate useEffect patterns)
- Extract buildCustomizeArgs, computeReorder pure helpers
- Extract CommitButton, ContextMenuOverlay, CustomizeOverlay
- isSelectionActive extracted as pure function (was closure)
- Sidebar reduced from 604 LoC/cc=34 to ~120 LoC/cc=11

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:37:33 +01:00
lucaronin
c3415e3ce0 refactor: extract Inspector sub-panels into InspectorPanels.tsx
Split Inspector.tsx (score 7.78) into:
- Inspector.tsx: slim shell with header, layout, prop wiring (score 9.6)
- InspectorPanels.tsx: DynamicRelationshipsPanel, BacklinksPanel,
  GitHistoryPanel, LinkButton, RelationshipGroup (score 8.87)

Extracted LinkButton as reusable component for both relationship
refs and backlinks. Moved resolveRefProps to simplify RelationshipGroup.
Extracted AddRelationshipForm and extractRelationshipRefs as focused units.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:22:26 +01:00
lucaronin
af94280e4d refactor: decompose NoteList into focused components and extract helpers
Break up NoteListInner (cc=61, 331 LoC, score 6.94) into:
- NoteItem: extracted item rendering component (score 9.63)
- SortDropdown: extracted into own file (score 9.68)
- noteListHelpers.ts: all utility functions (score 10.0)
  - GroupBuilder class replaces 5-arg addResolvedGroup
  - filterEntries split into filterByKind/filterByFilterType
- PinnedCard: unified entity/type document cards
- RelationshipGroupSection: extracted group rendering
- NoteList.tsx: orchestration only (score 8.79)

NoteListInner reduced from cc=61 to cc=23, 331 → 105 LoC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:18:05 +01:00
lucaronin
2692ca4a8b refactor: extract useTabManagement hook and mock frontmatter helpers from useNoteActions
Break up the monolithic useNoteActions hook (cc=37, 232 LoC, score 6.89) into:
- useTabManagement: owns tab state and all tab operations (score 9.53)
- mockFrontmatterHelpers: extracted and simplified YAML manipulation (score 9.09+)
- useNoteActions: now focused on note creation and frontmatter operations (score 9.22)

Key improvements:
- Extracted entryMatchesTarget() to reduce wikilink matching complexity
- Deduplicated replaceKeyInLines/removeKeyFromLines into processKeyInLines
- Converted buildNewEntry to use object param (was 5 positional args)
- Overall complexity reduced from cc=37 to cc=11 in useNoteActions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:11:52 +01:00
lucaronin
df3e4a787e feat: git history — clickable commit hashes with diff view 2026-02-22 10:02:35 +01:00
lucaronin
b0657bc3f5 feat: archive notes button + cmd+e shortcut 2026-02-22 10:02:17 +01:00
lucaronin
cc6d9e4799 fix: prevent double-fire of rename handler on Enter + blur
When pressing Enter in the inline tab rename input, handleSave was
called twice: once from the keydown handler and again from the blur
event triggered when React unmounted the input (editingPath → null).

The second rename call found the old path already deleted/moved,
producing empty content that overwrote the correctly renamed file
in MOCK_CONTENT — causing the editor to show a blank note.

Added a committedRef guard to InlineTabEdit so handleSave only
executes once per edit session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:53:35 +01:00
lucaronin
abf838640d fix: + buttons for note creation pass MouseEvent as type, causing crash
onClick={onCreateNote} passes the React MouseEvent as the first arg
to handleCreateNoteImmediate(type?), which treats it as a truthy type
string. generateUntitledName then calls .toLowerCase() on the event
object, throwing a TypeError and silently aborting note creation.

Wrap both NoteList and TabBar onClick handlers to call with no args.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:50:54 +01:00
lucaronin
ccf7a440f4 fix: add back-to-editor navigation from commit diff view
When viewing a commit diff from git history, the GitBranch toggle in the
breadcrumb bar was disabled if the file had no unstaged changes, leaving
no way to return to the editor. Now the toggle stays active whenever
diffMode is true, and a visible "Back to editor" button appears at the
top of the diff view for better discoverability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:47:41 +01:00
lucaronin
d23ce5a0b5 feat: clickable commit hashes in git history panel open diff view
- Add get_file_diff_at_commit Rust command to show diff at a specific commit
- Make shortHash in GitHistoryPanel a clickable button that opens DiffView
- Show author name below commit message (small, muted)
- Remove disabled "View all revisions" button
- Wire onLoadDiffAtCommit through Editor → Inspector → App.tsx
- Add mock handler for get_file_diff_at_commit in browser mode
- Fix pre-existing missing `order` field on trashed mock entries
- Update Inspector tests for new behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:27:18 +01:00
lucaronin
7effa256b4 feat: enable adding new relationships from the properties panel 2026-02-21 22:14:15 +01:00