Zander Hawke 8cdcb648a6 feat(tauri): D4 page model, two-flow index, and related fixes vor 9 Stunden
..
public ba3addc98b feat(desktop): Hearthside theme, empty day state, ephemeral drag handle vor 23 Stunden
src 8cdcb648a6 feat(tauri): D4 page model, two-flow index, and related fixes vor 9 Stunden
src-tauri 8cdcb648a6 feat(tauri): D4 page model, two-flow index, and related fixes vor 9 Stunden
.gitignore ac8ba1d93e feat: Tauri desktop app shell and editor cleanup vor 1 Tag
README.md 8cdcb648a6 feat(tauri): D4 page model, two-flow index, and related fixes vor 9 Stunden
nuxt.config.ts 77e97ec7e1 feat: desktop onboarding, native menu, sidebar overhaul, and real-time index suggestions vor 19 Stunden
package.json 8cdcb648a6 feat(tauri): D4 page model, two-flow index, and related fixes vor 9 Stunden
vitest.config.ts 77e97ec7e1 feat: desktop onboarding, native menu, sidebar overhaul, and real-time index suggestions vor 19 Stunden

README.md

Enesis

A warm, literary journal app. Built with Tauri, Nuxt UI, and the @enesis/editor block markdown engine.

Daily notes are flat markdown files (journals/YYYY-MM-DD.md) — readable anywhere, no lock-in. A SQLite index provides full-text search and backlinks.

The visual identity (Hearthside theme) leans into warmth: Lora serif, clay primary, stone neutrals, earthy semantics.

Quick start

pnpm install
pnpm generate  # static build
pnpm tauri dev  # or: pnpm tauri build

Stack

Layer Technology
Desktop shell Tauri v2 (Rust)
Frontend Nuxt 3 + Vue 3 + TypeScript
UI framework Nuxt UI v4
Editor engine @enesis/editor (Lezer-based block markdown)
Index SQLite via tauri-plugin-sql, FTS5
File watching notify crate

Theme — Hearthside

Warm, earthy, literary. Lora serif throughout, clay primary, orange secondary, stone neutral base, rose/emerald/amber/sky for semantics. Configured in app.config.ts and assets/main.css.

Development

pnpm dev              # Vite dev server
pnpm tauri dev        # Tauri desktop dev
pnpm generate         # Static pre-render (for Tauri)
pnpm test:e2e         # Playwright E2E (in packages/editor)

Project structure

src/
  assets/main.css              # Tailwind + theme tokens + font
  app.config.ts                # Nuxt UI color mappings
  components/
    JournalDay.vue             # One Editor per day
    JournalView.vue            # Reverse-chronological feed
    TimelineRail.vue           # Density dot timeline
    AppRightSidebar.vue        # References + search
  layouts/default.vue          # Sidebars + editor shell
  composables/                 # useFileSystem, useDates, etc.
  stores/                      # Pinia: workspace, ui
  lib/                         # Indexer, schema, parse helpers
src-tauri/
  src/lib.rs                   # Rust commands + plugins
  icons/                       # App icons (clay block logo)
public/
  favicon.svg                  # Clay block stacking logo

User guide — Editor behaviors

Auto-save

Content is saved to disk 500ms after you stop typing. Pending saves are flushed immediately when navigating away, so you won't lose edits from quick page switches.

Page creation

No file is created until you type. Clicking a [[link]] navigates to the page's path and shows an empty editor. The file is written to disk only when you add content. If you navigate away without typing, nothing is saved. This keeps your workspace clean of empty placeholder files.

The conventional location for new pages is the pages/ directory (e.g. pages/My Note.md). You can change this in Settings.

Renaming a page

The title at the top of a page is an editable <input> styled as a heading. Changing it renames the underlying file on disk. Press Enter to confirm, Escape to cancel. Note: existing [[links]] in other pages are not updated — that's a planned enhancement.

Tags

Clicking a #tag opens a virtual aggregation page showing all blocks that reference it, regardless of which file they're in. This is a read-only view — no file on disk until you type content here (future feature).

Backlinks

At the bottom of every page, a collapsible "Linked references" section shows which other pages link to this one. Each result includes the source page name and a contextual excerpt with the [[link]] highlighted. Clicking a backlink navigates to the source page.

Search (coming in D6)

Full-text search across the SQLite index is planned but not yet wired to the sidebar. The FTS5 index is built, the query logic is ready, but the UI is not connected.


Index two-flow architecture

The SQLite index is rebuilt on two paths with different trade-offs:

Flow 1 — App startup (syncIndex)

Runs every time the app launches. Avoids unnecessary work:

  1. List all .md files from the filesystem
  2. Read and hash each file (djb2, stored in pages.content_hash)
  3. Compare hash against the stored value for that file
  4. Unchanged → skip entirely (no splitMarkdownIntoBlocks, no SQL writes)
  5. New/changed → call indexFile (block-level diff, only writes changed blocks)
  6. Deleted → cascade remove from index (blocks, links, FTS)

Files whose content hasn't changed cost one hash computation and a SELECT query. No block parsing, no link extraction, no write amplification.

Flow 2 — Manual reindex (fullReindex)

Triggered via File → Reindex Library (Cmd+Shift+R). Wipes the entire index (DELETE FROM pages — FK cascade handles blocks, links, FTS) and re-indexes every .md file from scratch. Use when you suspect corruption or want a guaranteed clean state.

File watcher (incremental)

Between these two flows, the notify-based file watcher handles individual file changes:

  • Modified/createdindexFile on the single file
  • DeletedremoveFile (cascade)

The watcher starts before sync completes. If a file changes mid-sync, the watcher re-indexes it independently — worst case a stale entry that corrects on the next change.


Developer notes — D4 Page Model

For implementation details, data flow diagrams, and edge case documentation, see AGENTS.md in the repository root. Key sections:

  • Route architecture: catch-all [...slug].vue classifies paths (page vs tag)
  • Data flow: event chain from EditorBlock click → usePageNavigation → router push
  • Indexer title fallback: computePageTitle() ensures every page has a non-empty title
  • Edge cases: JSDoc @remarks on each component and function list deferred work

Tests

pnpm test              # vitest run in apps/tauri
biome check src/       # run from apps/tauri