Architecture Overview
notectl follows a strict layered architecture. Each layer has clear responsibilities and dependencies only flow downward.
Layer Diagram
Section titled “Layer Diagram”┌─────────────────────────────────────────┐│ editor/ │ Web Component (public API)├─────────────────────────────────────────┤│ plugins/ │ Feature modules├──────────┬──────────┬───────────────────┤│ commands/│ input/ │ view/ │ High-level ops, input, DOM├──────────┴──────────┴───────────────────┤│ state/ │ EditorState, Transaction, History├─────────────────────────────────────────┤│ model/ │ Immutable data types└─────────────────────────────────────────┘Layers
Section titled “Layers”Model (model/)
Section titled “Model (model/)”The foundation. Immutable data types with no DOM dependency:
Document— Root container withchildren: BlockNode[]BlockNode— Block-level nodes (paragraph, heading, list_item, etc.)TextNode— Inline text segments with marksInlineNode— Atomic inline elements (hard break, emoji, mention, etc.)Mark— Inline annotations (bold, italic, link, etc.)Selection— Anchor + head positionsSchema— Describes valid node and mark typesNodeSpec/MarkSpec— Type declarations with DOM rendering
All types are deeply readonly. Mutations create new instances.
State (state/)
Section titled “State (state/)”Manages editor state transitions:
EditorState— Immutable state container (doc + selection + schema + storedMarks)Transaction— A sequence of steps that transform stateTransactionBuilder— Fluent API for constructing transactionsStepApplication— Pure functions that apply steps to produce new stateHistory— Undo/redo via transaction inversion with grouping
View (view/)
Section titled “View (view/)”DOM management and user interaction:
EditorView— Orchestrates dispatch, reconciliation, and input handlingReconciler— Block-level DOM diffing (compares old/new state, patches DOM)SelectionSync— Syncs DOM selection with model selectionNodeView— Custom rendering interface for complex blocks (e.g., tables)
Input (input/)
Section titled “Input (input/)”Processes user input:
InputHandler—beforeinputevent → commandsKeyboardHandler— Key events → keymap dispatchPasteHandler— Clipboard paste handlingInputRule— Pattern-based text transforms (e.g.,#→ heading)
Commands (commands/)
Section titled “Commands (commands/)”High-level editing operations:
toggleMark,insertText,deleteBackward,deleteForwardsplitBlock,mergeBlockBackward,selectAllisMarkActive,sharesParent,isInsideIsolating
Plugins (plugins/)
Section titled “Plugins (plugins/)”Feature modules that compose the editor. Each plugin:
- Registers schema extensions (NodeSpec, MarkSpec)
- Registers commands and keymaps
- Registers input rules
- Registers toolbar items
- Reacts to state changes
Editor (editor/)
Section titled “Editor (editor/)”The public API layer:
NotectlEditor— Web Component (<notectl-editor>)createEditor()— Factory function
Key Design Principles
Section titled “Key Design Principles”DOM Isolation
Section titled “DOM Isolation”Only view/ touches the DOM. The model and state layers are purely data-driven and can run without a browser (useful for testing, server-side processing, etc.).
Immutability
Section titled “Immutability”All data in model/ and state/ is immutable. Changes produce new objects:
const newState = oldState.apply(transaction);// oldState is unchangedStep-Based Changes
Section titled “Step-Based Changes”Every change is expressed as a sequence of atomic Steps. Each step stores enough data to be inverted. This enables:
- Undo/redo without state snapshots
- Transaction composition
- Change inspection/auditing
Plugin Encapsulation
Section titled “Plugin Encapsulation”Plugins never access editor internals directly. Everything goes through PluginContext. This makes plugins:
- Testable in isolation
- Safe from breaking changes in internal APIs
- Composable without conflicts
Block-Level Reconciliation
Section titled “Block-Level Reconciliation”The Reconciler diffs at block granularity — if a block hasn’t changed, its DOM is untouched. Within a changed block, inline content is fully rebuilt. This is a good balance between performance and simplicity.