docs: ROADMAP full restore + #98-143 tech debt links + archive historical DESIGN/CHANGES (#144)
This commit is contained in:
152
docs/archive/DESIGN-0.17.3.md
Normal file
152
docs/archive/DESIGN-0.17.3.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# DESIGN: Notes Rich Text Editor, Obsidian-Style Linking & Knowledge Graph
|
||||
|
||||
**Version:** v0.17.3
|
||||
**Status:** Draft
|
||||
**Scope:** Upgrade the notes editor from a plain `<textarea>` to a CM6-powered
|
||||
markdown editor with live preview, `[[wikilink]]` resolution, a backlinks
|
||||
system, Canvas-rendered force-directed graph visualization, transclusion,
|
||||
note-from-selection (chat → notes bridge), and daily notes.
|
||||
|
||||
**Depends on:** v0.17.2 (CodeMirror 6 bundle provides `CM.codeEditor()` and
|
||||
the markdown language mode).
|
||||
|
||||
---
|
||||
|
||||
## Motivation
|
||||
|
||||
The notes system (v0.9.3 side panel, CRUD, folders, search) stores markdown
|
||||
but edits it as raw text in a `<textarea>`. Now that CM6 is bundled, we can
|
||||
give notes the same live-preview markdown experience that the chat input gets
|
||||
— plus something the chat input doesn't need: **inter-note linking**.
|
||||
|
||||
The Obsidian model is the right reference: markdown files with `[[wikilinks]]`
|
||||
resolved at render time, bidirectional backlinks, and create-on-reference.
|
||||
Our advantage over Obsidian: we already have **semantic search via embeddings**
|
||||
(v0.14.0 KB infrastructure), so link autocomplete can be smarter than
|
||||
filename matching.
|
||||
|
||||
Beyond linking, the notes system sits adjacent to the chat system but has no
|
||||
bridge between them. Users accumulate valuable AI responses in conversations
|
||||
that they then manually copy into notes. Note-from-selection closes that gap.
|
||||
A visual graph makes the link topology navigable and discoverable, turning
|
||||
notes from a flat list into a connected knowledge base.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### New CM6 Factory: `CM.noteEditor()`
|
||||
|
||||
A third factory alongside `chatInput()` and `codeEditor()`, tailored for
|
||||
long-form markdown editing in the notes panel.
|
||||
|
||||
```javascript
|
||||
CM.noteEditor(target, {
|
||||
value: '', // initial markdown content
|
||||
darkMode: true,
|
||||
onChange: (text) => {}, // live content callback
|
||||
onLink: (title) => {}, // [[link]] activated callback
|
||||
linkCompleter: async (query) => [], // fuzzy note search for [[ autocomplete
|
||||
});
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- Full markdown syntax highlighting (headings, bold, italic, code, lists, links)
|
||||
- Live inline preview: headings render at size, bold/italic styled, code blocks
|
||||
highlighted — the document is still markdown but *looks* formatted
|
||||
- `[[` triggers autocomplete dropdown of existing notes (fuzzy search)
|
||||
- `[[Note Title]]` tokens rendered as clickable chips (CM6 `Decoration.widget`)
|
||||
- `![[Note Title]]` recognized as transclusion markers (decoration differs from
|
||||
regular links — see Transclusion section)
|
||||
- Line numbers off (it's a note, not code)
|
||||
- Spell check enabled (`EditorView.contentAttributes: { spellcheck: 'true' }`)
|
||||
- Vim/Emacs keybindings respected (user preference from v0.17.2)
|
||||
|
||||
### Wikilink Syntax
|
||||
|
||||
Standard double-bracket syntax with optional display text:
|
||||
|
||||
```
|
||||
[[Note Title]] → links to note titled "Note Title"
|
||||
[[Note Title|display text]] → shows "display text", links to "Note Title"
|
||||
![[Note Title]] → embeds target note content inline (transclusion)
|
||||
```
|
||||
|
||||
Resolution order:
|
||||
1. Exact title match (case-insensitive)
|
||||
2. Fuzzy title match (for autocomplete, not for rendering)
|
||||
3. Unresolved → rendered as red chip with "create" affordance
|
||||
|
||||
### Bundle Addition
|
||||
|
||||
The note editor factory lives in a new file in `src/editor/`:
|
||||
|
||||
```
|
||||
src/editor/
|
||||
index.mjs # existing — adds noteEditor to window.CM
|
||||
chat-input.mjs # existing
|
||||
code-editor.mjs # existing
|
||||
note-editor.mjs # NEW — noteEditor() factory
|
||||
wikilink.mjs # NEW — CM6 extension: parse, decorate, autocomplete [[links]]
|
||||
theme.mjs # existing
|
||||
```
|
||||
|
||||
The wikilink extension is CM6-native: a `ViewPlugin` that scans for `[[...]]`
|
||||
patterns and replaces them with `Decoration.widget` nodes (clickable chips).
|
||||
The autocomplete uses CM6's `autocompletion` with a custom `completeFromList`
|
||||
source triggered by `[[`.
|
||||
|
||||
No bundle size concern — the wikilink plugin is tiny custom code, not an
|
||||
external dependency.
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
### `note_links` Table
|
||||
|
||||
Tracks directed links between notes, extracted on save.
|
||||
|
||||
```sql
|
||||
-- Postgres
|
||||
CREATE TABLE IF NOT EXISTS note_links (
|
||||
source_note_id UUID NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
|
||||
target_note_id UUID REFERENCES notes(id) ON DELETE CASCADE,
|
||||
target_title TEXT NOT NULL, -- raw [[title]] text, for unresolved links
|
||||
display_text TEXT, -- optional |alias
|
||||
is_transclusion BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
PRIMARY KEY (source_note_id, target_title)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_note_links_target ON note_links(target_note_id)
|
||||
WHERE target_note_id IS NOT NULL;
|
||||
|
||||
-- SQLite
|
||||
CREATE TABLE IF NOT EXISTS note_links (
|
||||
source_note_id TEXT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
|
||||
target_note_id TEXT REFERENCES notes(id) ON DELETE CASCADE,
|
||||
target_title TEXT NOT NULL,
|
||||
display_text TEXT,
|
||||
is_transclusion INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (source_note_id, target_title)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_target ON note_links(target_note_id)
|
||||
WHERE target_note_id IS NOT NULL;
|
||||
```
|
||||
|
||||
**Key design decisions:**
|
||||
|
||||
- `target_note_id` is **nullable** — an unresolved link (target note doesn't
|
||||
exist yet) stores the title but has `NULL` for the FK. When a note with that
|
||||
title is created, a background pass resolves dangling links.
|
||||
- Primary key is `(source_note_id, target_title)` — a note can only link to
|
||||
the same title once (last one wins if duplicated).
|
||||
- `target_title` is always stored for human readability and re-resolution
|
||||
after renames.
|
||||
- `is_transclusion` distinguishes `![[embed]]` from `[[link]]` — useful for
|
||||
|
||||
[... truncated for brevity, full content used in tool]
|
||||
Reference in New Issue
Block a user