Skip to content

Commit f3c85ca

Browse files
feat: add Notes Space with unified architecture (#698)
* feat(notes): add CodeMirror 6 dependencies * refactor(notes): make NotesEditor standalone with defineModel * feat(spaces): add dev space with editor demo * fix(notes): hide header mark with trailing space, show code marks on block focus * feat(notes): add rounded corners and padding to fenced code blocks * fix(notes): show marks only when cursor is between them, not on entire line * chore(dev): expand demo markdown content with all supported elements * fix(notes): use primary color for links instead of accent * feat(notes): add tab/shift-tab indentation support * feat(notes): add smart list indentation with ordered list renumbering * docs: add Notes Space design spec * docs: update Notes Space spec after review Address review findings: - Watcher integration: ignore __spaces__/notes/ in snippet sync - Own getNotesPaths() function, separate from snippet getPaths() - Own notesRuntimeRef singleton cache - Storage contracts and useStorage() integration - Notes-specific reserved names (not reusing snippet list) - Separate content update endpoint (PATCH /notes/:id/content) - Note serialization: frontmatter + raw body (no fragment parsing) - Composables re-export from index.ts - notesState as separate electron-store object * docs: address final review findings in Notes Space spec - Scope watcher ignore to __spaces__/notes/ only (not __spaces__/ broadly) - Use separate useNotesStorage() accessor instead of extending StorageProvider - Own writeNoteFolderMetadata() (FolderRecord has defaultLanguage, NoteFolderRecord doesn't) - Reuse shared pendingStateWriteByPath for state debounce - Note to add 'notes' case in refreshAfterStorageSync() switch * docs: add Notes Space implementation plan * feat(notes): add notes storage types and contracts Add NotesPaths, NotesState, MarkdownNote, NotesRuntimeCache and other runtime types. Add notes contracts (NoteRecord, NotesStorage, NotesFoldersStorage, NoteTagsStorage, NotesStorageProvider) to the shared contracts file. * feat(notes): add notes constants, paths, and validation support Add getNotesPaths(), notesRuntimeRef singleton, notes-specific path constants. Add folder path utilities (buildNotesFolderPathMap, findNotesFolderById, etc.). Extend validateEntryName to accept 'note' kind. * feat(notes): add notes state persistence with shared debounce Add loadNotesState, saveNotesState, ensureNotesStateFile that reuse the shared pendingStateWriteByPath maps from the snippet runtime. State version starts at 1. * feat(notes): add note file I/O, serialization, and folder metadata Add serializeNote (YAML frontmatter + raw markdown body), readNoteFromFile, writeNoteToFile, persistNote, loadNotes, findNoteById, listNoteMarkdownFiles. Add notes folder metadata read/write without legacy migration or defaultLanguage. * feat(notes): add notes search index with trigram matching Add buildNotesSearchIndex, getNoteIdsBySearchQuery, and invalidateNotesSearchIndex using the same trigram + word token algorithm as snippet search. * feat(notes): add notes sync, runtime cache, and barrel export Add syncNotesFoldersWithDisk, syncNotesRuntimeWithDisk, getNotesRuntimeCache, resetNotesRuntimeCache for the notes storage runtime. Add barrel export index.ts for the notes runtime module. * fix(notes): add missing META_FILE_NAME re-export in notes constants * feat(notes): add notes folders CRUD storage * feat(notes): add notes CRUD storage * feat(notes): add notes tags storage, provider index, and barrel export * feat(notes): add useNotesStorage accessor and ignore notes in snippet watcher * feat(notes): add API DTOs for notes, note-folders, note-tags * feat(notes): add REST API routes for notes, note-folders, note-tags * feat(notes): register notes space, routing, and i18n keys * feat(notes): add notes composables (useNotes, useNoteFolders, useNoteTags, useNotesApp) * feat(notes): add 3-column notes space layout * feat(notes): add notes sidebar with library and folder tree * feat(notes): add virtualized notes list with search and context menu * feat(notes): add notes editor pane connecting CM6 to composable * feat(notes): add notes case to storage sync handler When external vault changes are detected (e.g. sync from another device), the notes space now refreshes folders, notes, and tags data. * fix(notes): replace (api as any) casts with typed API method calls * fix(notes): fix api:generate script and regenerate API client with notes endpoints * fix(notes): add response schema to notes GET endpoint for proper API typing * fix(notes): add external drop handler to move notes into folders via drag-drop * feat(notes): add editor header with note name input and action buttons slot * fix(notes): re-create editor when switching notes via key binding * fix(notes): invalidate search index on state save so search finds new/updated notes * fix(notes): invalidate search index when note content is updated * fix(notes): rewrite search to match code space pattern with substring fallback - Add w: token fallback when trigrams yield no candidates (short words) - Use intersectSets for proper candidate narrowing - Final check uses searchText.includes(normalizedQuery) for substring match - ensureNotesSearchIndex now returns cache for caller use * fix(notes): return empty trigrams for words < 3 chars to match code space behavior * fix(notes-search): support one-character queries * fix(notes-search): harden runtime cache behavior * fix(notes): close note-space parity gaps * chore: merge branch 'main' into feat/notes-editor-cm6-clean * fix(notes-ui): align tags panel placement with code space * feat: add tag input
1 parent 682b402 commit f3c85ca

File tree

180 files changed

+18123
-2058
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+18123
-2058
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ node_modules
88
components.d.ts
99
auto-imports.d.ts
1010
.github/*-instructions.md
11-
.env
11+
.env
12+
docs/superpowers
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Markdown Runtime Reuse Playbook
2+
3+
A concise guide for adding new markdown-backed entities without duplicating infrastructure.
4+
5+
---
6+
7+
## 1. Required Shared Primitives
8+
9+
When adding a new markdown entity, you **must** use the following from `runtime/shared/` instead of reimplementing them.
10+
11+
### `shared/path.ts`
12+
13+
| Export | Purpose |
14+
|--------|---------|
15+
| `toPosixPath` | Normalize OS paths to forward-slash form |
16+
| `depthOfRelativePath` | Count directory depth of a relative path |
17+
| `normalizeDirectoryPath` | Ensure trailing slash, posix-normalized |
18+
| `listMarkdownFiles` | Walk a directory and return `.md` file paths |
19+
20+
### `shared/folderIndex.ts`
21+
22+
| Export | Purpose |
23+
|--------|---------|
24+
| `buildFolderPathMap` | Build `folderId → path` lookup from folder list |
25+
| `buildPathToFolderIdMap` | Build `path → folderId` reverse lookup |
26+
| `findFolderByIdPure` | Find a folder in a list by ID (no side effects) |
27+
| `getFolderSiblings` | Return all folders sharing the same parent |
28+
| `getNextFolderOrder` | Compute the next `order` value within a sibling set |
29+
30+
### `shared/searchIndex.ts`
31+
32+
| Export | Purpose |
33+
|--------|---------|
34+
| `normalizeSearchValue` | Lowercase + trim a query string |
35+
| `splitSearchWords` | Tokenize a query into words |
36+
| `createWordTrigrams` | Generate trigram set for a word |
37+
| `buildSearchTokens` | Produce the full token set for an entity field |
38+
| `intersectSets` | Return the intersection of two `Set<string>` objects |
39+
40+
### `shared/stateWriter.ts`
41+
42+
| Export | Purpose |
43+
|--------|---------|
44+
| `scheduleStateFlush` | Debounce a pending write by path key |
45+
| `flushPendingStateWrites` | Immediately flush all pending writes (used on exit) |
46+
| `registerStateWriteHooks` | Register `process` exit/signal hooks once per entity |
47+
48+
---
49+
50+
## 2. File Structure for New Entities
51+
52+
```
53+
entity/
54+
runtime/
55+
constants.ts — entity-specific constants (e.g., file prefix, debounce ms), runtimeRef
56+
types.ts — entity-specific runtime types (internal, not API DTOs)
57+
paths.ts — thin wrappers over shared/folderIndex, with entity-scoped cache
58+
search.ts — entity search built on shared/searchIndex primitives
59+
state.ts — entity state persistence using shared/stateWriter
60+
sync.ts — disk sync logic (watch + reconcile)
61+
index.ts — barrel: re-export everything the routes need
62+
storages/
63+
notes.ts / items.ts — CRUD operations for leaf records
64+
folders.ts — folder CRUD + tree operations
65+
tags.ts — tag CRUD (if applicable)
66+
index.ts — factory function returning the full storage object
67+
```
68+
69+
**Rules:**
70+
71+
- `constants.ts` owns the single `runtimeRef` for this entity. Nothing else creates one.
72+
- `paths.ts` **wraps** `buildFolderPathMap` / `buildPathToFolderIdMap`; it does not reimplement them.
73+
- `state.ts` calls `registerStateWriteHooks` exactly once (guard with a module-level boolean).
74+
- `index.ts` in `storages/` exports one factory; route files call that factory, not individual modules.
75+
76+
---
77+
78+
## 3. Minimum Required Tests
79+
80+
Every new entity must ship with the following test files before merging:
81+
82+
| File | What it covers |
83+
|------|---------------|
84+
| `files.test.ts` | `listMarkdownFiles`, path helpers, directory walking edge cases |
85+
| `sync.test.ts` | Full sync cycle: write files to a temp vault, run sync, assert DB state |
86+
| `search.test.ts` | Token building, trigram matching, multi-word intersection |
87+
| `storages/*.test.ts` | CRUD contract: create, read, update, delete, conflict, not-found |
88+
89+
Tests must use a real temp directory (not mocks) for sync and file-walker tests.
90+
91+
---
92+
93+
## 4. API Contract Checklist
94+
95+
Before shipping a route, verify every case:
96+
97+
| Scenario | Expected response |
98+
|----------|------------------|
99+
| PATCH with empty body | `{ invalidInput: true, notFound: false }` |
100+
| GET / PATCH / DELETE with missing ID | `{ notFound: true }` or throw `NotFoundError` |
101+
| Create/rename with a name that already exists in the same parent | `throwStorageError('NAME_CONFLICT')` |
102+
| Create with a `folderId` that does not exist | `throwStorageError('FOLDER_NOT_FOUND')` |
103+
104+
---
105+
106+
## 5. Anti-Patterns
107+
108+
Avoid the following when adding a new entity:
109+
110+
- **Copying helpers** — never copy `search`, `state`, or `path` helpers into the new entity's directory. Import from `shared/`.
111+
- **Premature abstraction** — do not introduce a new shared utility until it has a second real, distinct consumer.
112+
- **Silent fallback on bad input** — e.g., resolving an unknown `folderId` to `null` and continuing silently. Throw `FOLDER_NOT_FOUND` instead.
113+
- **Skipping exit hooks**`registerStateWriteHooks` in `state.ts` must be called during entity initialization; omitting it causes data loss on unclean exit.
114+
- **Duplicating `runtimeRef`** — each entity owns exactly one `runtimeRef` declared in its `constants.ts`.

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,19 @@
4343
"prepare": "simple-git-hooks && npm run rebuild"
4444
},
4545
"dependencies": {
46+
"@codemirror/commands": "^6.10.3",
47+
"@codemirror/lang-markdown": "^6.5.0",
48+
"@codemirror/language": "^6.12.2",
49+
"@codemirror/language-data": "^6.5.2",
50+
"@codemirror/state": "^6.6.0",
51+
"@codemirror/view": "^6.40.0",
4652
"@dagrejs/dagre": "^1.1.5",
4753
"@elysiajs/cors": "^1.2.0",
4854
"@elysiajs/node": "^1.4.2",
4955
"@elysiajs/swagger": "^1.3.1",
5056
"@faker-js/faker": "^10.1.0",
57+
"@lezer/highlight": "^1.2.3",
58+
"@lezer/markdown": "^1.6.3",
5159
"@sinclair/typebox": "^0.34.41",
5260
"@vue-flow/background": "^1.3.2",
5361
"@vue-flow/controls": "^1.1.3",
@@ -162,5 +170,6 @@
162170
},
163171
"volta": {
164172
"node": "20.16.0"
165-
}
173+
},
174+
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8"
166175
}

0 commit comments

Comments
 (0)