@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.
- package/LICENSE +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- 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.
|