@liminis/editor 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +376 -0
  3. package/dist/annotations/anchor-align.d.ts +50 -0
  4. package/dist/annotations/anchor-align.js +74 -0
  5. package/dist/annotations/anchor-model.d.ts +81 -0
  6. package/dist/annotations/anchor-model.js +80 -0
  7. package/dist/annotations/anchor-resolver.d.ts +92 -0
  8. package/dist/annotations/anchor-resolver.js +231 -0
  9. package/dist/annotations/block-structure.d.ts +65 -0
  10. package/dist/annotations/block-structure.js +113 -0
  11. package/dist/annotations/types.d.ts +159 -0
  12. package/dist/annotations/types.js +50 -0
  13. package/dist/annotations.d.ts +25 -0
  14. package/dist/annotations.js +27 -0
  15. package/dist/app/App.d.ts +64 -0
  16. package/dist/app/App.js +180 -0
  17. package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
  18. package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
  19. package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
  20. package/dist/app/editor/AnchorScrollPlugin.js +134 -0
  21. package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
  22. package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
  23. package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
  24. package/dist/app/editor/AnnotationPlugin.js +135 -0
  25. package/dist/app/editor/AnnotationSurface.d.ts +34 -0
  26. package/dist/app/editor/AnnotationSurface.js +115 -0
  27. package/dist/app/editor/AssetContext.d.ts +20 -0
  28. package/dist/app/editor/AssetContext.js +81 -0
  29. package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
  30. package/dist/app/editor/BlockClickPlugin.js +126 -0
  31. package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
  32. package/dist/app/editor/CodeBlockPlugin.js +219 -0
  33. package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
  34. package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
  35. package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
  36. package/dist/app/editor/DragHandlePlugin.js +300 -0
  37. package/dist/app/editor/Editor.d.ts +84 -0
  38. package/dist/app/editor/Editor.js +539 -0
  39. package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
  40. package/dist/app/editor/FrontmatterPlugin.js +301 -0
  41. package/dist/app/editor/ImageModal.d.ts +13 -0
  42. package/dist/app/editor/ImageModal.js +267 -0
  43. package/dist/app/editor/ImagePlugin.d.ts +3 -0
  44. package/dist/app/editor/ImagePlugin.js +187 -0
  45. package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
  46. package/dist/app/editor/LinkClickPlugin.js +90 -0
  47. package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
  48. package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
  49. package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
  50. package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
  51. package/dist/app/editor/SearchPlugin.d.ts +1 -0
  52. package/dist/app/editor/SearchPlugin.js +379 -0
  53. package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
  54. package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
  55. package/dist/app/editor/SlashMenu.d.ts +11 -0
  56. package/dist/app/editor/SlashMenu.js +494 -0
  57. package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
  58. package/dist/app/editor/SlashMenuPlugin.js +168 -0
  59. package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
  60. package/dist/app/editor/TableActionsPlugin.js +398 -0
  61. package/dist/app/editor/TogglePlugin.d.ts +1 -0
  62. package/dist/app/editor/TogglePlugin.js +159 -0
  63. package/dist/app/editor/Toolbar.d.ts +10 -0
  64. package/dist/app/editor/Toolbar.js +263 -0
  65. package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
  66. package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
  67. package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
  68. package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
  69. package/dist/app/editor/annotation-marks.d.ts +177 -0
  70. package/dist/app/editor/annotation-marks.js +750 -0
  71. package/dist/app/editor/annotationCommands.d.ts +13 -0
  72. package/dist/app/editor/annotationCommands.js +11 -0
  73. package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
  74. package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
  75. package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
  76. package/dist/app/editor/c4/edge-clipping.js +139 -0
  77. package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
  78. package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
  79. package/dist/app/editor/c4/layout.d.ts +25 -0
  80. package/dist/app/editor/c4/layout.js +839 -0
  81. package/dist/app/editor/c4/parser.d.ts +19 -0
  82. package/dist/app/editor/c4/parser.js +410 -0
  83. package/dist/app/editor/c4/render-to-string.d.ts +24 -0
  84. package/dist/app/editor/c4/render-to-string.js +34 -0
  85. package/dist/app/editor/c4/renderer.d.ts +64 -0
  86. package/dist/app/editor/c4/renderer.js +569 -0
  87. package/dist/app/editor/c4/types.d.ts +203 -0
  88. package/dist/app/editor/c4/types.js +43 -0
  89. package/dist/app/editor/correction-yaml.d.ts +10 -0
  90. package/dist/app/editor/correction-yaml.js +40 -0
  91. package/dist/app/editor/editorNodes.d.ts +9 -0
  92. package/dist/app/editor/editorNodes.js +36 -0
  93. package/dist/app/editor/index.d.ts +1 -0
  94. package/dist/app/editor/index.js +1 -0
  95. package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
  96. package/dist/app/editor/mark-overlap-resolver.js +45 -0
  97. package/dist/app/editor/nodes/C4Component.d.ts +14 -0
  98. package/dist/app/editor/nodes/C4Component.js +284 -0
  99. package/dist/app/editor/nodes/C4Node.d.ts +32 -0
  100. package/dist/app/editor/nodes/C4Node.js +112 -0
  101. package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
  102. package/dist/app/editor/nodes/CalloutNode.js +94 -0
  103. package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
  104. package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
  105. package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
  106. package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
  107. package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
  108. package/dist/app/editor/nodes/CustomListNode.js +54 -0
  109. package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
  110. package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
  111. package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
  112. package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
  113. package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
  114. package/dist/app/editor/nodes/EquationComponent.js +130 -0
  115. package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
  116. package/dist/app/editor/nodes/EquationNode.js +179 -0
  117. package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
  118. package/dist/app/editor/nodes/FootnoteNode.js +151 -0
  119. package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
  120. package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
  121. package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
  122. package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
  123. package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
  124. package/dist/app/editor/nodes/HtmlNode.js +121 -0
  125. package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
  126. package/dist/app/editor/nodes/ImageComponent.js +228 -0
  127. package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
  128. package/dist/app/editor/nodes/ImageNode.js +183 -0
  129. package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
  130. package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
  131. package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
  132. package/dist/app/editor/nodes/MermaidComponent.js +216 -0
  133. package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
  134. package/dist/app/editor/nodes/MermaidNode.js +96 -0
  135. package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
  136. package/dist/app/editor/nodes/ToggleNode.js +237 -0
  137. package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
  138. package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
  139. package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
  140. package/dist/app/editor/nodes/diagram-utils.js +29 -0
  141. package/dist/app/editor/nodes/index.d.ts +30 -0
  142. package/dist/app/editor/nodes/index.js +17 -0
  143. package/dist/app/editor/utils.d.ts +2 -0
  144. package/dist/app/editor/utils.js +17 -0
  145. package/dist/app/mapper/index.d.ts +3 -0
  146. package/dist/app/mapper/index.js +2 -0
  147. package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
  148. package/dist/app/mapper/lexicalToMdast.js +1937 -0
  149. package/dist/app/mapper/mdastToLexical.d.ts +31 -0
  150. package/dist/app/mapper/mdastToLexical.js +1075 -0
  151. package/dist/contract.d.ts +8 -0
  152. package/dist/contract.js +8 -0
  153. package/dist/headless.d.ts +40 -0
  154. package/dist/headless.js +45 -0
  155. package/dist/host/context.d.ts +20 -0
  156. package/dist/host/context.js +24 -0
  157. package/dist/host/defaults.d.ts +21 -0
  158. package/dist/host/defaults.js +47 -0
  159. package/dist/host/messages.d.ts +26 -0
  160. package/dist/host/messages.js +47 -0
  161. package/dist/host/types.d.ts +75 -0
  162. package/dist/host/types.js +13 -0
  163. package/dist/index.d.ts +35 -0
  164. package/dist/index.js +51 -0
  165. package/dist/markdown/annotate-sentinels.d.ts +42 -0
  166. package/dist/markdown/annotate-sentinels.js +86 -0
  167. package/dist/markdown/parse.d.ts +56 -0
  168. package/dist/markdown/parse.js +516 -0
  169. package/dist/markdown/stringify.d.ts +7 -0
  170. package/dist/markdown/stringify.js +439 -0
  171. package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
  172. package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
  173. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
  174. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
  175. package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
  176. package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
  177. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
  178. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
  179. package/dist/markdown.d.ts +28 -0
  180. package/dist/markdown.js +44 -0
  181. package/dist/mathjax-config.d.ts +89 -0
  182. package/dist/mathjax-config.js +133 -0
  183. package/dist/nodes.d.ts +34 -0
  184. package/dist/nodes.js +34 -0
  185. package/dist/stores/correctionStore.d.ts +15 -0
  186. package/dist/stores/correctionStore.js +12 -0
  187. package/dist/stores/editorStore.d.ts +16 -0
  188. package/dist/stores/editorStore.js +27 -0
  189. package/dist/styles.css +2477 -0
  190. package/dist/types.d.ts +296 -0
  191. package/dist/types.js +91 -0
  192. package/dist/utils/file-types.d.ts +10 -0
  193. package/dist/utils/file-types.js +46 -0
  194. package/docs/annotations.md +134 -0
  195. package/docs/decisions/README.md +72 -0
  196. package/docs/decisions/adr-002.md +27 -0
  197. package/docs/decisions/adr-007.md +27 -0
  198. package/docs/decisions/adr-010.md +26 -0
  199. package/docs/decisions/adr-024.md +124 -0
  200. package/docs/decisions/adr-024b.md +0 -0
  201. package/docs/decisions/adr-025.md +103 -0
  202. package/docs/decisions/adr-027.md +66 -0
  203. package/docs/decisions/adr-057.md +67 -0
  204. package/docs/decisions/adr-070.md +131 -0
  205. package/docs/decisions/adr-075.md +410 -0
  206. package/docs/decisions/adr-076.md +183 -0
  207. package/docs/decisions/adr-077.md +410 -0
  208. package/docs/decisions/adr-078.md +243 -0
  209. package/docs/decisions/adr-079.md +167 -0
  210. package/docs/decisions/adr-080.md +325 -0
  211. package/docs/decisions/adr-081.md +108 -0
  212. package/docs/decisions/adr-082.md +123 -0
  213. package/docs/decisions/adr-083.md +204 -0
  214. package/docs/decisions/adr-084.md +179 -0
  215. package/docs/editor-api.md +106 -0
  216. package/docs/markdown-pipeline.md +282 -0
  217. package/docs/provenance.md +106 -0
  218. package/docs/zusammen-editor-capability-map.md +232 -0
  219. package/package.json +175 -0
@@ -0,0 +1,27 @@
1
+ # ADR-007: IPC via contextBridge API (2026-01-25)
2
+
3
+ **Context:**
4
+ - Electron security model requires isolating renderer from Node.js
5
+ - Need to expose main process functionality to React UI
6
+ - Want type-safe API for all IPC calls
7
+ - Many different domains (fs, agent, chat, settings, etc.)
8
+
9
+ **Decision:**
10
+ - Use `contextBridge.exposeInMainWorld` to create `window.api`
11
+ - Organize API by domain: `api.fs.*`, `api.agent.*`, `api.chat.*`, etc.
12
+ - Use `ipcRenderer.invoke` for request/response
13
+ - Use `ipcRenderer.on` for events (streaming, file changes)
14
+ - Define TypeScript types in `window-api.d.ts`
15
+
16
+ **Alternatives Considered:**
17
+ - Enable nodeIntegration → Rejected: security risk, Electron anti-pattern
18
+ - Single IPC channel with message types → Rejected: harder to type, less discoverable
19
+ - GraphQL-like schema → Rejected: overkill for desktop app
20
+
21
+ **Consequences:**
22
+ - ✅ Secure by default (no Node.js in renderer)
23
+ - ✅ Discoverable API through types
24
+ - ✅ Clean domain separation
25
+ - ✅ Support for both invoke (request/response) and events (streaming)
26
+ - ❌ Verbose preload script
27
+ - ❌ Must maintain types for both ends
@@ -0,0 +1,26 @@
1
+ # ADR-010: Bidirectional Markdown Conversion (mdast ↔ Lexical) (2026-01-25)
2
+
3
+ **Context:**
4
+ - Source of truth is markdown files on disk
5
+ - Editor operates on Lexical node tree
6
+ - Need to preserve markdown formatting on round-trip
7
+ - Support for extensions: wiki-links, frontmatter, math, GFM tables
8
+
9
+ **Decision:**
10
+ - Parse markdown to mdast (Markdown Abstract Syntax Tree) using unified/remark ecosystem
11
+ - Convert mdast → Lexical nodes for editing (`mdastToLexical.ts`)
12
+ - Convert Lexical nodes → mdast for saving (`lexicalToMdast.ts`)
13
+ - Use micromark extensions for wiki-links, frontmatter, math, GFM
14
+
15
+ **Alternatives Considered:**
16
+ - Direct markdown ↔ Lexical → Rejected: mdast provides cleaner intermediate representation
17
+ - Store Lexical JSON as source → Rejected: lose markdown portability
18
+ - Markdown-only editing (no WYSIWYG) → Rejected: poor UX for notes app
19
+
20
+ **Consequences:**
21
+ - ✅ Markdown files remain portable
22
+ - ✅ Clean separation of parsing/serialization from editing
23
+ - ✅ Can add new markdown extensions via mdast plugins
24
+ - ✅ Preserve formatting on round-trip
25
+ - ❌ Two conversion layers add complexity
26
+ - ❌ Some edge cases in bidirectional conversion
@@ -0,0 +1,124 @@
1
+ # ADR-024: UI Consistency - Custom Dialogs Over Native Browser Functions (2026-01-29)
2
+
3
+ **Context:**
4
+ - PR #54 (right-click context menu for document rename/delete) required confirmation dialogs
5
+ - Native browser functions (`window.confirm`, `window.alert`, `window.prompt`) available but have UX limitations:
6
+ - Non-customizable styling (doesn't match app design system)
7
+ - Blocks entire application (modal blocking at OS level)
8
+ - Can't be themed (always system default appearance)
9
+ - No loading states or async actions
10
+ - Inconsistent appearance across platforms
11
+ - Gemini Code Assist review comment highlighted the UX inconsistency concern
12
+ - Need architectural guidance for future development
13
+
14
+ **Decision:**
15
+ - **ALWAYS use shadcn/ui components** for all UI elements throughout the application
16
+ - **NEVER use native browser functions** (`window.confirm`, `window.alert`, `window.prompt`)
17
+ - **NEVER use unstyled HTML elements** directly (use shadcn/ui wrappers instead)
18
+ - Use shadcn/ui components with consistent styling:
19
+ - `Dialog` + `DialogContent` for modal interactions
20
+ - `Button` for all clickable actions (with appropriate variants)
21
+ - `Input` and `Textarea` for form fields
22
+ - `Badge` for status indicators
23
+ - `ScrollArea` for custom scrollable regions
24
+ - `ContextMenu` for right-click interactions
25
+ - Component patterns:
26
+ - `Dialog` + `DialogHeader` + `DialogTitle` + `DialogDescription` for context
27
+ - `DialogFooter` with `Button` components for actions
28
+ - Variant system: `variant="destructive"` for dangerous actions, `variant="outline"` for secondary
29
+ - Loading states: `disabled={isLoading}` with dynamic button text
30
+ - Apply this pattern to ALL user interactions: dialogs, confirmations, forms, buttons, menus, etc.
31
+
32
+ **Rationale:**
33
+ - Visual consistency: All dialogs match application design system and theme
34
+ - Better UX: Can show loading states, error messages, contextual information
35
+ - Accessibility: Proper ARIA labels, keyboard navigation via Radix UI primitives
36
+ - Flexibility: Can customize layout, add checkboxes ("Don't ask again"), show warnings
37
+ - Professional appearance: Custom dialogs elevate perceived quality
38
+
39
+ **Current State:**
40
+ - ✅ PR #54 correctly implements custom Dialog for delete/rename confirmations
41
+ - ❌ One violation: `RemarkablePanel.tsx` line 277 uses `window.confirm` for uninstall confirmation
42
+
43
+ **Alternatives Considered:**
44
+ - Allow native dialogs for "quick confirmations" → Rejected: Creates inconsistency, same effort to use Dialog component
45
+ - Hybrid approach (custom for complex, native for simple) → Rejected: Hard to define boundary, leads to inconsistent UX
46
+ - Only enforce for "important" actions → Rejected: All user interactions deserve consistent treatment
47
+
48
+ **Consequences:**
49
+ - ✅ Consistent, professional user experience across all interactions
50
+ - ✅ Themeable dialogs (dark mode, custom branding)
51
+ - ✅ Better accessibility via Radix UI primitives
52
+ - ✅ Can show loading states and error handling inline
53
+ - ✅ More control over layout and information hierarchy
54
+ - ❌ Slightly more code than native `window.confirm()` (but minimal with shadcn components)
55
+ - ❌ Need to manage dialog state (open/close, target data)
56
+
57
+ **Implementation Pattern:**
58
+
59
+ ```tsx
60
+ // State management
61
+ const [confirmDialogOpen, setConfirmDialogOpen] = useState(false)
62
+ const [confirmTarget, setConfirmTarget] = useState<DataType | null>(null)
63
+ const [isProcessing, setIsProcessing] = useState(false)
64
+
65
+ // Trigger confirmation
66
+ const handleActionClick = (item: DataType) => {
67
+ setConfirmTarget(item)
68
+ setConfirmDialogOpen(true)
69
+ }
70
+
71
+ // Handle confirmed action
72
+ const handleConfirm = async () => {
73
+ if (!confirmTarget) return
74
+
75
+ setIsProcessing(true)
76
+ try {
77
+ await performAction(confirmTarget)
78
+ setConfirmDialogOpen(false)
79
+ // Success handling
80
+ } catch (error) {
81
+ // Error handling
82
+ } finally {
83
+ setIsProcessing(false)
84
+ }
85
+ }
86
+
87
+ // Dialog component
88
+ <Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
89
+ <DialogContent>
90
+ <DialogHeader>
91
+ <DialogTitle>Confirm Action</DialogTitle>
92
+ <DialogDescription>
93
+ Are you sure you want to perform this action?
94
+ </DialogDescription>
95
+ </DialogHeader>
96
+ <DialogFooter>
97
+ <Button
98
+ variant="outline"
99
+ onClick={() => setConfirmDialogOpen(false)}
100
+ disabled={isProcessing}
101
+ >
102
+ Cancel
103
+ </Button>
104
+ <Button
105
+ variant="destructive"
106
+ onClick={handleConfirm}
107
+ disabled={isProcessing}
108
+ >
109
+ {isProcessing ? 'Processing...' : 'Confirm'}
110
+ </Button>
111
+ </DialogFooter>
112
+ </DialogContent>
113
+ </Dialog>
114
+ ```
115
+
116
+ **Action Items:**
117
+ - [ ] Fix `RemarkablePanel.tsx` uninstall confirmation to use Dialog component
118
+ - [ ] Document this pattern in component library or contributing guide
119
+ - [ ] Add ESLint rule to warn on `window.confirm`, `window.alert`, `window.prompt` usage
120
+
121
+ **References:**
122
+ - PR #54: Document rename/delete with custom dialogs (exemplar implementation)
123
+ - Gemini Code Assist review comment on PR #54
124
+ - shadcn/ui Dialog: https://ui.shadcn.com/docs/components/dialog
File without changes
@@ -0,0 +1,103 @@
1
+ # ADR-025: Zustand for Renderer-Side State Management (2026-01-30)
2
+
3
+ **Context:**
4
+ - PR #70 initially used `window` custom events for frontmatter UI communication between `EditorColumn` (toolbar) and `FrontmatterPlugin` (editor)
5
+ - Pattern: `window.dispatchEvent(new CustomEvent('frontmatter:toggle'))` and `window.addEventListener('frontmatter:state', handler)`
6
+ - This worked but had architectural issues:
7
+ - Global event listeners make data flow difficult to trace
8
+ - No type safety for event payloads
9
+ - Unclear relationships between components
10
+ - Hard to debug (events scattered across global scope)
11
+ - Not idiomatic React architecture
12
+ - Code review feedback identified this as an anti-pattern to avoid
13
+ - Both components are in the same React tree (SlashMDApp rendered as child component)
14
+
15
+ **Decision:**
16
+ - **ALWAYS use Zustand stores for renderer-side state management**
17
+ - **NEVER use window events for React component communication**
18
+ - Create focused stores for specific domains (e.g., `editorStore`, `navigationStore`)
19
+ - Use Zustand's selector pattern for granular subscriptions
20
+ - Store pattern:
21
+ ```typescript
22
+ interface EditorState {
23
+ // State properties
24
+ hasFrontmatter: boolean
25
+ isFrontmatterOpen: boolean
26
+
27
+ // Actions (methods that update state)
28
+ setHasFrontmatter: (has: boolean) => void
29
+ toggleFrontmatter: () => void
30
+ reset: () => void
31
+ }
32
+
33
+ export const useEditorStore = create<EditorState>((set, get) => ({
34
+ hasFrontmatter: false,
35
+ isFrontmatterOpen: false,
36
+
37
+ setHasFrontmatter: (has: boolean) => set({ hasFrontmatter: has }),
38
+ toggleFrontmatter: () => set({ isFrontmatterOpen: !get().isFrontmatterOpen }),
39
+ reset: () => set({ hasFrontmatter: false, isFrontmatterOpen: false })
40
+ }))
41
+ ```
42
+ - Component usage:
43
+ ```typescript
44
+ // Read specific state slice
45
+ const isOpen = useEditorStore((state) => state.isFrontmatterOpen)
46
+
47
+ // Get action
48
+ const toggle = useEditorStore((state) => state.toggleFrontmatter)
49
+
50
+ // Use in handler
51
+ <Button onClick={toggle}>Toggle</Button>
52
+ ```
53
+
54
+ **When to Use Zustand:**
55
+ - State shared between multiple React components in renderer process
56
+ - UI state (navigation, editor modes, panel visibility)
57
+ - Client-side derived state (selections, filters, temporary UI flags)
58
+ - State that doesn't need to persist to disk or main process
59
+
60
+ **When NOT to Use Zustand:**
61
+ - Main process state (chat history, file system, agents) → Use IPC with reactive events (see ADR-024)
62
+ - Form state for single component → Use `useState`
63
+ - Transient animations/hover states → Use `useState`
64
+ - State synchronized with main process → Use IPC invoke + IPC events
65
+
66
+ **Window Events Are Still Valid For:**
67
+ - Cross-context communication where Zustand can't reach (different window instances, web workers)
68
+ - Integration with non-React code (legacy libraries, browser APIs)
69
+ - NOT for React-to-React component communication
70
+
71
+ **Alternatives Considered:**
72
+ - Window custom events → Rejected: poor traceability, no type safety, not idiomatic React
73
+ - Redux → Rejected: too much boilerplate for renderer-only state
74
+ - React Context → Rejected: performance issues with frequent updates, prop drilling
75
+ - Component callbacks → Rejected: tight coupling, doesn't scale
76
+
77
+ **Consequences:**
78
+ - ✅ Explicit, traceable data flow via React DevTools
79
+ - ✅ TypeScript type safety for state and actions
80
+ - ✅ Granular subscriptions (components only re-render when their slice changes)
81
+ - ✅ Simple API (less boilerplate than Redux)
82
+ - ✅ Works across component boundaries without prop drilling
83
+ - ✅ Easy to test (store is independent of components)
84
+ - ✅ Consistent with existing `navigationStore` pattern
85
+ - ❌ Additional file per domain (acceptable trade-off for clarity)
86
+ - ❌ Learning curve for Zustand patterns (minimal, simpler than Redux)
87
+
88
+ **Implementation Example from PR #70:**
89
+ - Created `editorStore.ts` for editor UI state
90
+ - Refactored `EditorColumn.tsx` to use `useEditorStore` instead of window events
91
+ - Refactored `FrontmatterPlugin.tsx` to update store directly instead of dispatching events
92
+ - Removed all `window.addEventListener` and `dispatchEvent` calls
93
+
94
+ **Store Organization:**
95
+ - Location: `src/renderer/stores/`
96
+ - Naming: `{domain}Store.ts` (e.g., `editorStore.ts`, `navigationStore.ts`)
97
+ - One store per domain (don't create monolithic global store)
98
+ - Keep stores focused and cohesive
99
+
100
+ **References:**
101
+ - Zustand docs: https://github.com/pmndrs/zustand
102
+ - PR #70: Config/Agents/Skills navigation refactor
103
+ - Existing pattern: `navigationStore.ts`
@@ -0,0 +1,66 @@
1
+ # ADR-027: Test Framework Strategy - Vitest + Playwright (2026-02-02)
2
+
3
+ **Context:**
4
+ - Liminis has grown to ~39K LOC with zero test coverage (greenfield)
5
+ - Complex Electron architecture: main process (IPC, file ops, Claude SDK) + renderer (React 19, Lexical editor)
6
+ - Need comprehensive testing strategy covering unit, integration, component, and E2E tests
7
+ - Build system uses Vite via electron-vite, package.json has `"type": "module"` (ESM)
8
+ - React 19 requires compatible testing libraries
9
+ - TypeScript 5.9 with strict mode throughout
10
+
11
+ **Decision:**
12
+ - Adopt **3-tier testing pyramid**: 60% unit, 25% integration, 15% E2E
13
+ - **Vitest** for unit and integration tests:
14
+ - Native ESM support (matches project `"type": "module"`)
15
+ - Vite integration (already using Vite via electron-vite)
16
+ - Fast parallel execution
17
+ - Compatible with Testing Library ecosystem
18
+ - **Playwright** for E2E tests:
19
+ - Official Electron testing recommendation
20
+ - Full app launch via `_electron.launch()`
21
+ - Video recording, multi-window support
22
+ - **React Testing Library** for component tests:
23
+ - User-behavior focused testing
24
+ - React 19 compatible (@testing-library/react@16.x)
25
+ - Runs on Vitest
26
+ - **Test structure**: Co-located tests in `__tests__/` directories, integration in `tests/`, E2E in `e2e/`
27
+ - **Key patterns**:
28
+ - Extract business logic from IPC handlers for testability
29
+ - Mock Claude SDK to avoid real API calls
30
+ - Use temp directories for file system isolation
31
+ - Test markdown layer, not Lexical editor internals
32
+
33
+ **Alternatives Considered:**
34
+ - Jest → Rejected: Complex ESM configuration, slower than Vitest, less modern
35
+ - Spectron → Rejected: Deprecated by Electron team in favor of Playwright
36
+ - Cypress → Rejected: Electron support requires workarounds, Playwright is official recommendation
37
+ - Keep no tests → Rejected: Codebase complexity demands automated testing
38
+
39
+ **Consequences:**
40
+ - ✅ Fast unit tests (<1s) for rapid iteration
41
+ - ✅ Seamless Vite integration (no build config changes)
42
+ - ✅ Modern testing stack (Vitest + Playwright actively maintained)
43
+ - ✅ Type-safe tests with full TypeScript support
44
+ - ✅ Coverage reporting via Vitest's built-in v8 provider
45
+ - ✅ CI/CD ready (GitHub Actions examples in strategy doc)
46
+ - ❌ Learning curve for team (but minimal - Vitest similar to Jest)
47
+ - ❌ Need to write ~200-300 tests to reach 60% coverage target
48
+ - ❌ Initial setup investment (1-2 weeks)
49
+
50
+ **Implementation Plan:**
51
+ - Phase 1: Infrastructure setup (Vitest/Playwright configs, CI)
52
+ - Phase 2: Critical path tests (20-30 tests covering key workflows)
53
+ - Phase 3: Expand coverage (component tests, all IPC handlers, E2E journeys)
54
+ - Phase 4: CI/CD integration (GitHub Actions, coverage gates)
55
+
56
+ **Coverage Targets:**
57
+ - Unit: 70% of utilities, stores, business logic
58
+ - Integration: 100% of critical IPC handlers
59
+ - E2E: 5-10 critical user journeys
60
+ - Overall: 60%+ code coverage
61
+
62
+ **References:**
63
+ - Detailed strategy: `docs/testing-strategy.md`
64
+ - Electron testing docs: https://www.electronjs.org/docs/latest/tutorial/automated-testing
65
+ - Playwright Electron API: https://playwright.dev/docs/api/class-electron
66
+ - Vitest: https://vitest.dev/
@@ -0,0 +1,67 @@
1
+ # ADR-057: Knowledge Corrections — File Format and Renderer→Filesystem→MCP Pipeline (2026-04-23)
2
+
3
+ > Documents the `.liminis/knowledge-corrections.yaml` file format and the architecture of the inline correction flow introduced by the right-click Correction panel.
4
+
5
+ **Context:**
6
+
7
+ - Meeting transcripts frequently mis-transcribe names (e.g. "Tito" instead of "Tyto"). Before this feature, a user who spotted the error had to edit the document manually and had no way to push the correction into the knowledge graph.
8
+ - The knowledge-writer MCP server already implements `knowledge_apply_corrections`, which reads a corrections file and merges entities. The file format was defined in the framework SKILL.md but no renderer code wrote to it.
9
+ - A new UI path is needed: right-click selected text → open correction panel → confirm → write YAML → sync graph.
10
+
11
+ **Decision:**
12
+
13
+ Introduce `.liminis/knowledge-corrections.yaml` as a first-class workspace artifact written by the renderer and consumed by the knowledge-writer MCP server.
14
+
15
+ **File format:**
16
+
17
+ ```yaml
18
+ corrections:
19
+ - type: same_as
20
+ canonical: "Tyto"
21
+ aliases:
22
+ - "Tito"
23
+ - "Tyto Ornaments"
24
+ - type: same_as
25
+ canonical: "Ritesh"
26
+ aliases:
27
+ - "Ratesh"
28
+ ```
29
+
30
+ - `type: same_as` — entity merging: the `aliases` are all treated as the same entity as `canonical`.
31
+ - `type: retract` — fact invalidation (not written by the correction panel; reserved for future use).
32
+ - `applied_at` — optional timestamp written by `knowledge_apply_corrections` after it processes an entry. Entries with `applied_at` are skipped on subsequent runs.
33
+
34
+ **Renderer→filesystem→MCP pipeline:**
35
+
36
+ 1. User right-clicks selected text in the Lexical editor and chooses "Correction…".
37
+ 2. `SelectionContextMenuPlugin` calls `useCorrectionStore.getState().open()` — a Zustand action (ADR-025).
38
+ 3. `CorrectionPanelPlugin` (a Lexical plugin inside `LexicalComposer`) reads the Zustand store and renders a floating panel.
39
+ 4. Panel mounts immediately. In parallel it fires:
40
+ - `window.api.fs.readFile('.liminis/knowledge-corrections.yaml')` — parsed in the renderer using the `yaml` package
41
+ - `window.api.mcp.callTool('knowledge-reader', 'knowledge_find_entities', ...)` — async, loads progressively
42
+ - `window.api.mcp.callTool('knowledge-reader', 'knowledge_search_passages', ...)` — async, loads progressively
43
+ 5. On confirm, the renderer:
44
+ a. Optionally replaces all occurrences of the alias in the open document via `editor.update()` (Lexical history entry is created; undo reverts the document but not the YAML — this is intentional).
45
+ b. Calls `window.api.fs.mkdir('.liminis')` to ensure the directory exists.
46
+ c. Merges the new entry into the parsed YAML and writes atomically via `window.api.fs.writeFile(..., 'atomic')` (ADR-055).
47
+ d. Calls `window.api.mcp.callTool('knowledge-writer', 'knowledge_apply_corrections', {})`.
48
+ e. If the MCP call fails, shows an error toast; the YAML write is never blocked on MCP availability.
49
+
50
+ **Why YAML parsing in the renderer (not a dedicated main-process handler):**
51
+
52
+ The `yaml` package is pure JavaScript — Vite bundles it into the renderer without configuration. Using the existing `fs:readFile` / `fs:writeFile` IPC channels avoids adding a new IPC surface. The renderer merges entries in memory (a simple array operation) and writes the result atomically. A main-process handler would add indirection with no architectural benefit.
53
+
54
+ **Why `CorrectionPanelPlugin` lives inside `LexicalComposer`:**
55
+
56
+ Text replacement via `editor.update()` requires access to the Lexical editor instance, which is only available inside the `LexicalComposer` context via `useLexicalComposerContext()`. Placing the panel as a Lexical plugin (alongside `SearchPlugin`, `FrontmatterPlugin`) is consistent with existing practice and avoids passing a replacement callback from `EditorColumn`.
57
+
58
+ **Graceful degradation:**
59
+
60
+ - If the knowledge-writer MCP server is unavailable, the YAML write still succeeds. An error toast informs the user: "The correction was saved. It will be applied on the next ingestion pass." The `knowledge_apply_corrections` tool is idempotent and will process the pending entries the next time it is called (e.g., on the next document ingestion).
61
+ - If `.liminis/knowledge-corrections.yaml` does not exist, it is created on first use. The `fs:mkdir` call is a no-op if the directory already exists.
62
+
63
+ **Consequences:**
64
+
65
+ - `.liminis/knowledge-corrections.yaml` is the single source of truth for name corrections. Do not introduce a second store.
66
+ - Future features that write corrections (e.g., bulk import, retract entries) must append to this file using the same format.
67
+ - The `applied_at` field is written by the Python MCP server, not by the renderer. The renderer must preserve existing entries (including `applied_at` values) when merging — the `mergeCorrection` helper does this.
@@ -0,0 +1,131 @@
1
+ # ADR-070: Dedicated Lexical Node Types vs. Paragraph-Pattern Detection for Lossless Markdown Round-Tripping
2
+
3
+ **Date:** 2026-07-12
4
+ **Status:** Accepted
5
+ **Issue:** #906
6
+
7
+ ## Context
8
+
9
+ Liminis's editor round-trips markdown through `parse.ts` -> `mdastToLexical.ts` ->
10
+ [Lexical editor state] -> `lexicalToMdast.ts` -> `stringify.ts`. Several markdown
11
+ constructs have no native Lexical equivalent, so the import side must invent an
12
+ in-editor representation and the export side must recognize it and reconstruct the
13
+ original mdast node. This codebase has, by now, used both of the two available
14
+ strategies for that gap, with materially different outcomes:
15
+
16
+ 1. **Dedicated custom Lexical node type(s)** — `CalloutNode` (GitHub-style
17
+ `> [!NOTE]` blockquotes) and `ToggleContainerNode`/`ToggleTitleNode`/
18
+ `ToggleContentNode` (`<details>`/`<summary>`) each introduce purpose-built
19
+ `ElementNode` subclasses, registered in `editorNodes` (`Editor.tsx` and the
20
+ round-trip test harness), with `importJSON`/`exportJSON`/`importDOM`/`exportDOM`.
21
+ Export-side detection is a type check (`$isCalloutNode`, `$isToggleContainerNode`)
22
+ — unambiguous by construction.
23
+
24
+ 2. **Marker/pattern detection on plain nodes** — `footnoteDefinition` support
25
+ (indent=1 paragraphs, the first prefixed with a `FootnoteNode` label) and the
26
+ #897 list-item fix (a pair of bare, adjacent `LineBreakNode`s marking a
27
+ paragraph boundary inside a list item) reuse existing node types and detect a
28
+ *shape* on export, with no new node class or registration needed.
29
+
30
+ Issue #906 (definition lists flattened to a bold-term paragraph + plain paragraphs
31
+ on round-trip) needed one of these two strategies chosen for a third construct.
32
+ Research surfaced a specific asymmetry: the existing (lossy) `defList` import
33
+ already marks a definition-list term by toggling **bold** — a generic Lexical
34
+ format bit that ordinary user content can also carry. Pattern (2) is only safe
35
+ when the detected shape cannot occur except as the output of the reciprocal
36
+ import step. The `footnoteDefinition` marker (a dedicated `FootnoteNode`) satisfies
37
+ that; a real user paragraph cannot accidentally contain one. But this codebase's
38
+ own fixture corpus already documents a case where pattern (2) failed that test:
39
+ `other-list-item-double-hard-break` (`fixtures/roundtrip/README.md`) — the #897
40
+ fix's two-bare-`LineBreakNode` marker is also produced by ordinary content (a line
41
+ ending in `\` immediately followed by a line that is only `\`), so on export the
42
+ detector cannot distinguish "list-item paragraph boundary" from "one paragraph
43
+ with two literal hard breaks," and silently reformats the latter.
44
+
45
+ Reusing **bold formatting** as an implicit term marker for definition lists would
46
+ repeat that exact failure mode, but with a much more common trigger: any ordinary
47
+ bold paragraph followed by an indented paragraph — plausible real-world content —
48
+ would misparse as a former definition list on export, corrupting unrelated
49
+ documents (violating this project's no-regression bar for round-trip fidelity,
50
+ tracked here via the fixture corpus's `FR-007`-style requirement).
51
+
52
+ ## Decision
53
+
54
+ **Use dedicated custom Lexical node types for definition lists**:
55
+ `DefinitionListNode` (container, `<dl>`), `DefinitionTermNode` (`<dt>`, holds
56
+ phrasing content directly like `HeadingNode`), and `DefinitionDescriptionNode`
57
+ (`<dd>`, holds block content like `ToggleContentNode`) — modeled directly on the
58
+ `CalloutNode`/`ToggleContainerNode` precedent, registered in `editorNodes` in both
59
+ `Editor.tsx` and `roundtrip-test-utils.ts`.
60
+
61
+ **General rule for future constructs:** prefer a dedicated node type whenever the
62
+ candidate marker for pattern-detection is (or reuses) a *generic* Lexical
63
+ primitive — a format bit (bold/italic/etc.), an indent level, a bare structural
64
+ node (`LineBreakNode`) — that ordinary user-authored content could also produce.
65
+ Pattern detection remains an acceptable lighter-weight choice only when the marker
66
+ is itself dedicated and unique (e.g., a `FootnoteNode` label that only the
67
+ footnote import path ever creates), so the shape genuinely cannot arise except
68
+ from the reciprocal import.
69
+
70
+ The one-time cost of a dedicated node type — a new `ElementNode` subclass or
71
+ three, `importJSON`/`exportJSON`/`importDOM`/`exportDOM`, and registration in two
72
+ `editorNodes` arrays — is small, bounded, and non-recurring per construct. It
73
+ eliminates the false-positive class of bug entirely, rather than trading it for a
74
+ "probably rare enough" argument that this codebase's own fixture corpus has
75
+ already shown to fail in practice.
76
+
77
+ ## Alternatives Considered
78
+
79
+ **Reuse the existing bold-paragraph + indented-paragraph marker, detected on
80
+ export.** Rejected: the marker (bold formatting) is a generic format bit that
81
+ real, unrelated user paragraphs can carry, and the closely analogous
82
+ `other-list-item-double-hard-break` gap already demonstrates this class of bug in
83
+ this exact codebase. Continuing to add pattern-detection converters on top of a
84
+ demonstrated false-positive mechanism would compound the risk rather than close it.
85
+
86
+ **Give definition-list term paragraphs a new, more specific marker (e.g., a
87
+ dedicated zero-width marker node prefixing the term, analogous to
88
+ `FootnoteNode`).** Not pursued: this still requires a new node type (to make the
89
+ marker unique), at which point representing the term/description structure with
90
+ dedicated nodes directly — rather than a marker plus post-hoc paragraph
91
+ regrouping — is simpler and mirrors the mdast shape (`defList` /
92
+ `defListTerm` / `defListDescription`) one-to-one.
93
+
94
+ ## Consequences
95
+
96
+ **Positive:**
97
+ - Zero false-positive risk: `$isDefinitionListNode`/`$isDefinitionTermNode`/
98
+ `$isDefinitionDescriptionNode` are exact type checks, not shape heuristics.
99
+ - The Lexical-side representation mirrors mdast's own `defList` shape (flat
100
+ `defListTerm`/`defListDescription` siblings under one container), so
101
+ `defListToMarkdown` — already wired into `stringify.ts` — needed no *node-type*
102
+ changes; only a handler-level spacing override (see Neutral, below) was needed.
103
+ - Establishes an explicit, documented decision rule (above) for the next time this
104
+ fork comes up, instead of re-litigating the trade-off from scratch or silently
105
+ defaulting to whichever pattern the most recently touched file happens to use.
106
+
107
+ **Negative / trade-offs:**
108
+ - Three new node classes and two `editorNodes` registration sites (`Editor.tsx`,
109
+ `roundtrip-test-utils.ts`) to maintain going forward — boilerplate that a
110
+ pattern-detection approach would have avoided.
111
+ - No live-editor WYSIWYG affordances (slash-menu entry, toolbar button,
112
+ Enter-key UX) were added for definition lists — out of scope for this fix — so
113
+ the new node types are currently reachable only via markdown import/paste, not
114
+ by typing a definition list from scratch in the editor.
115
+
116
+ **Neutral:**
117
+ - `mdast-util-definition-list`'s `defListToMarkdown` normalizes the description
118
+ marker to `:` followed by exactly three spaces (a four-column indent) on every
119
+ export, regardless of the original input's marker spacing — a pre-existing
120
+ property of that third-party serializer, independent of this ADR's node-type
121
+ choice. Since the issue's own reproduction and the spec's acceptance scenarios
122
+ use the single-space `: ` convention, `stringify.ts` overrides just the
123
+ `defListDescription` handler (alongside its existing `emphasis`/`strong`/
124
+ `wikiLink` handler overrides) to emit `: ` instead of `: `, so byte-identical
125
+ round-tripping works for that convention without needing to special-case
126
+ fixture spacing.
127
+
128
+ ## Update log
129
+
130
+ - **2026-07-12** (issue #906): initial adoption, for definition-list round-trip
131
+ fidelity.