ISSUES.md 8.4 KB

Known Issues & Deferred Work

Tracked at source via @remarks JSDoc tags. This file consolidates them for planning. Last updated: 2026-06-22 (D4). Paths are relative to repo root.


Cross-file links / rename

1. [[link]] targets stale after rename

Files: apps/tauri/src/components/PageView.vue:26, apps/tauri/src/composables/usePageNavigation.ts:33, apps/tauri/src/composables/useSidebarIndex.ts:10

Renaming a page does not update existing [[links]] in other files that reference the old title. Backlink counts also reflect the stale title.

  • Risk: Users navigate to dead pages via stale links
  • Fix: After rename, query SQLite for all links.target = oldTitle, update file contents and re-index
  • Deferred because: No cross-file mutation pattern exists yet

2. Block ref scroll target not implemented

File: apps/tauri/src/pages/[...slug].vue:43

#block-{id} is appended to the URL when navigating to a block ref (((block-id))), but the receiving PageView has no onMounted scroll-to logic. The hash fragment is in the URL for future use only.

  • Risk: Low — user lands at top of page instead of scrolled to block
  • Fix: onMounted → check route.hash, query DOM for [data-block-id], scrollIntoView()
  • Deferred because: Block refs are not yet first-class navigation targets

Data integrity

3. No mtime check on save

Files: apps/tauri/src/pages/[...slug].vue:36, components/JournalDay.vue

Auto-save always overwrites the file on disk regardless of whether an external editor changed it between debounce triggers.

  • Risk: Silent data loss if user edits the same file externally
  • Fix: Compare file mtime before writing; show conflict dialog on mismatch
  • Deferred because: Low probability in practice (single-user app)

4. modified_at is index-time, not file mtime

Files: apps/tauri/src/components/TagView.vue:19, apps/tauri/src/lib/indexer.ts:70

blocks.modified_at is set to Date.now() / 1000 during indexing, not the file's mtime from fs::metadata. Blocks from the same file indexed in the same pass share the same timestamp, making ORDER BY modified_at unreliable.

  • Risk: Tag view and block-ref ordering appear random within a single index pass
  • Fix: Store file mtime from fs::metadata during indexing
  • Deferred because: Requires plumbing mtime through the indexer's incremental update path

5. Existing workspaces may have empty-page-title rows

File: apps/tauri/src/lib/indexer.ts:67

Workspaces indexed before the computePageTitle fallback was added (D4) may have pages.title = '' for pages without an # H1. These won't be fixed until a fullReindex (app restart) or incremental re-index on file change.

  • Risk: Some pages invisible in sidebar, some [[links]] don't resolve
  • Fix: One-time SQL migration: UPDATE pages SET title = ... WHERE title = ''
  • Deferred because: Existing test workspaces are small; no user data yet

Rendering / display edge cases

6. Excerpt String.slice() can split multi-byte characters

Files: components/PageView.vue:22, apps/tauri/src/pages/[...slug].vue:40

Excerpt extraction uses String.slice() (UTF-16 code units). If a [[link]] offset falls inside a 4-byte character (e.g. emoji), the excerpt may be truncated mid-character, producing a replacement character (�).

  • Risk: Cosmetic — garbled excerpt text in backlinks panel
  • Fix: Code-point-aware iteration via Array.from() or Intl.Segmenter
  • Deferred because: Low frequency; emoji in page titles is rare

7. TagView has no pagination (50-result limit)

File: components/TagView.vue:24

The tag aggregation query uses LIMIT 50 with no cursor/offset pagination. If a tag has hundreds of references, only the last 50 are returned.

  • Risk: Incomplete tag results for popular tags
  • Fix: Add cursor-based pagination with "load more" button
  • Deferred because: No workspace has tags with >50 references yet

8. safeName() doesn't handle all Unicode edge cases

File: apps/tauri/src/composables/usePageNavigation.ts:35

safeName strips <>:?*"\ characters but doesn't handle zero-width spaces, RTL markers, or other Unicode control characters. These are rare in page names but could produce surprising URL paths.

  • Risk: Very low — edge-case Unicode in filenames is uncommon
  • Fix: Normalize via .normalize('NFC') and filter Unicode control character ranges
  • Deferred because: Not encountered in practice

9. Tag names lowercased during extraction

File: components/TagView.vue:23

Links are extracted with lowercased tag names (see extractLinks in parse.ts). Mixed-case tags from a prior index version may not match.

  • Risk: Low — consistent lowercasing since D1 means all tags are already lowercased
  • Fix: One-time UPDATE tags SET name = LOWER(name) migration
  • Deferred because: Already consistent in current data

Editor — Indent/Outdent

13. Shift+Tab / Tab outdent-indent doesn't reach the handler

Files: packages/editor/src/composables/useBlockKeyboardHandlers.ts:132, packages/editor/src/components/EditorBlock.vue:668, packages/editor/src/components/Editor.vue:1085

Tab/Shift+Tab indent/outdent handling is wired end-to-end (handler in useBlockKeyboardHandlers, pure operations in editor-operations.ts, event handling in Editor.vue) but the browser may capture the Tab key for focus navigation before it reaches the ProseMirror handleKeyDown prop, which is set at EditorBlock.vue:668.

  • Risk: Blocks can't be outdented. Deeply nested lists can only go deeper via Tab (if it happens to work) but can never come back up.
  • Fix options:
    • Shift+Tab via handleKeyDown: Ensure event.preventDefault() is called and the PM EditorView DOM element captures Tab — check tabindex and contenteditable interaction.
    • Alternative — Enter on empty indented block: If a block has depth > 0 and its content is empty when Enter is pressed, outdent one level instead of creating a new empty block at the same depth. This is how many outliners (Logseq, Roam, Workflowy) handle it.
    • Explorer-style wrap at depth 0: If Tab at depth 0 cycles to depth 0 (no-op), reconsider to start indenting.
  • Diagnosis needed: Whether handleKeyDown fires at all for Tab, or if the browser / PM default captures it first.
  • Deferred because: Not yet diagnosed.

Missing features

10. Block ID stamp creation not wired

File: apps/tauri/src/lib/indexer.ts:74

The indexer tracks has_id_stamp per block but the stamp creation (id:: <nanoid> written to the file when a ((ref)) is created) is deferred to D2. The indexer is read-only and never modifies files.

  • Risk: Block refs can't be created; ((ref)) syntax works in the editor but there's no way to assign a stable ID to a block yet
  • Deferred because: D2 scope (read-only index) was prioritised over write

11. No search UI wired to FTS5 index

File: Sidebar (apps/tauri/src/components/AppSidebar.vue, not yet implemented)

Full-text search via SQLite FTS5 is built into the schema (pages_fts, blocks_fts) and the indexer populates it. No search input or results UI exists in the sidebar yet.

  • Risk: Users can't search their notes
  • Deferred because: D6 scope

12. Insertion zones still stubbed

File: packages/editor/src/components/EditorBlock.vue (core editor)

Click targets between blocks (FIXME: Replace with Editor shell insertion zone) are not yet materialised as visual insertion zones. Boundary navigation (ArrowUp from first block, ArrowDown from last) creates paragraphs inline as a temporary workaround.

  • Risk: None functional, but UX is rougher than final design
  • Deferred because: Part of the Editor shell redesign, not the page model

Future enhancements (not bugs)

These are tracked for completeness but not yet scoped into any milestone.

Item Area Notes
Automatic [[link]] update on rename Cross-file Requires write-lock pattern
Conflict dialog on external file change Auto-save Low priority for single-user
Paginated backlinks / tag view UI Simple cursor pattern
Block ref scroll-to Navigation Depends on block ID stamp
Full-text search UI Sidebar FTS5 already built
File-mtime-based block ordering Indexer Requires fs::metadata