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/ │ decorations/ │ State + visual annotations├─────────────────────┴───────────────────┤│ serialization/ │ style/ │ i18n/ │ Cross-cutting concerns├──────────────────┴──────────┴──────────┤│ model/ │ platform/ │ Immutable data types + env└─────────────────────┴──────────────────┘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:
InputManager— Orchestrates all input handler lifecycle and delegationInputHandler—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, split by category:
- Text editing —
insertText,splitBlock,mergeBlockBackward,mergeBlockForward,selectAll - Mark operations —
toggleMark,toggleBold,toggleItalic,toggleUnderline,isMarkActive - Deletion —
deleteBackward,deleteForward,deleteWordBackward,deleteWordForward,deleteSoftLineBackward,deleteSoftLineForward - Movement (model) —
moveCharacterForward,moveCharacterBackward,moveToBlockStart,moveToBlockEnd,moveToDocumentStart,moveToDocumentEnd,extendCharacterForward,extendCharacterBackward,extendToDocumentStart,extendToDocumentEnd - Movement (view) —
moveWordForward,moveWordBackward,moveToLineStart,moveToLineEnd,moveLineUp,moveLineDown,extendWordForward,extendWordBackward,extendToLineStart,extendToLineEnd,extendLineUp,extendLineDown - Utilities —
sharesParent,isInsideIsolating,isVoidBlock,canCrossBlockBoundary
Decorations (decorations/)
Section titled “Decorations (decorations/)”Lightweight visual annotations that do not alter the document model:
Decoration— Inline, node, or widget decorations rendered by the view layerDecorationSet— Container for managing decoration collectionsPositionMapping— Maps decoration positions across state transitions
Serialization (serialization/)
Section titled “Serialization (serialization/)”HTML import/export:
DocumentSerializer— Converts Document to HTMLDocumentParser— Parses HTML into DocumentCSSClassCollector— Collects CSS classes for class-based export
Style (style/)
Section titled “Style (style/)”Runtime style management:
StyleRuntime— Dynamic stylesheet injection and management
I18n (i18n/)
Section titled “I18n (i18n/)”Internationalization support:
LocaleService— Locale resolution and managementLocale— Supported locale constants (9 languages)
Platform (platform/)
Section titled “Platform (platform/)”Environment detection:
- Browser and OS detection utilities for platform-specific behavior
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
Internally decomposed into focused controllers:
EditorLifecycleCoordinator— Init/destroy state machine and readiness trackingEditorConfigController— Runtime config and observed attribute managementEditorEventEmitter— Typed event emitter (stateChange,focus,blur, etc.)EditorStyleCoordinator— Shadow DOM stylesheet managementEditorThemeController— Theme application and CSS variable generationPaperLayoutController— Paper size and layout managementPluginBootstrapper— Auto-registration of essential plugins and toolbar processingContentSerializer— Module of pure functions for content serialization (JSON, HTML, plain text)
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.