Architecture and maintenance
Separation of responsibilities
The core document/session model is independent of the browser DOM. Commands mutate the model through validated operations and history. The surface adapter translates native browser input/selection to model commands and renders current state. The public editor coordinates policy, codecs, preview, imports and host notifications.
The DOM is a projection. Do not save element.innerHTML or use execCommand as the application command architecture. Native browser editing is intercepted/normalized, with DOM reconciliation as an input fallback; the model remains authoritative.
Runtime modules
| File | Responsibility |
|---|---|
codecs.js |
Markdown parse/serialize, source metadata and safe URL helpers |
core.js |
Sessions, source/rich documents, positions, selections, history, revisions, bookmarks |
commands.js |
Editing operations, block/mark operations, input rules, search/comment helpers |
documents.js |
Supported HTML import/export and structured document helpers |
structures.js |
Lists, tables, cell navigation, image blocks, structural replacement |
policy.js |
Presets, feature compatibility, diagnostics and import handling |
markdown-commands.js |
Markdown Source bold/italic delimiter operations |
preview.js |
Preview preprocessing/sanitization, iframe, CSS, mapping and divider |
languages.js |
Lexical source decoration and bracket-matching helper |
paste.js |
Clipboard import/input fallback helpers |
surface.js |
contenteditable rendering/input, composition, native selection, measurement |
editor.js |
Base component lifecycle, API, command routing and events |
advanced.js |
Current extended editor: policy, structures, images, preview override |
image-host.js |
Optional sample native/browser image transport |
app.js, feature-ui.js |
Demonstration host UI and optional demo bridge |
styles.css, features.css |
Surface and demonstration styling, structure/preview rules |
Load order matters because modules augment shared globals/prototypes. advanced.js replaces CODEEditor.Editor/create; instantiate only after it loads. The globals are not an ES module dependency system.
Document shapes
A source document has a text string. A rich document contains ordered blocks with stable IDs and marked text runs. Supported block kinds include paragraph, headings, list/task items, quote, code, thematic break, raw fragment, cell and image. Structure metadata connects related list/table nodes and builds a normalized structural index.
Runs carry text plus marks such as bold/italic/code/link and supported style values. A table is represented through cell blocks with shared table/row/column metadata; it is not arbitrary nested HTML. An image is a block with reference/state metadata. Opaque raw content preserves unsupported source without pretending to offer editable semantics.
Rich selections use block IDs and offsets; source uses string offsets. Helpers flatten rich positions when needed for search, native transport and range operations. Source metadata and fingerprints allow unchanged source to be retained; edited structures can be serialized canonically.
Transactions and rendering
An edit captures model/selection state, validates the mutation and stores history. Source transaction batches validate all ranges against one revision. History is bounded and adjacent typing can be grouped. A rebuild can replace the surface without discarding this state.
The current surface uses browser contenteditable and browser selection/caret layout. It does not draw its own blinking caret. Some rendering paths replace substantial DOM content and measurement/colorizing can scan the document. There is no viewport virtualization, piece table optimized for huge files, worker parser, or semantic language engine.
Composition requires special handling: rerendering during an IME's intermediate state can destroy input. The adapter protects composition and reconciles the committed result. Operations needing committed values must respect that lifecycle.
Preview is separate from serialization
Preview hooks receive a serialized snapshot of content but return display-only substitutions or HTML. They do not mutate the document. The standard output path adds measurement anchors. Arbitrary host HTML cannot automatically retain source mapping, so following falls back to proportional scrolling.
Preview-only CSS lives in the iframe. Source/visual editor display CSS remains separate. Keeping these paths separate lets a blog preview imitate a site without leaking that site's CSS into the input surface.
Native wrapper layout
| File | Responsibility |
|---|---|
TextEditor.cs |
UserControl, WebView2 setup, queue, state cache, method bridge, lifecycle |
TextEditor.Properties.cs |
Typed forwarding properties |
TextEditor.Preview.cs |
Preview properties/events and async reverse requests |
EditorOptions.cs |
Settings, validation, notifications and wire values |
EditorTypes.cs |
Public enums, records and event arguments |
NativeImages.cs |
Bounded image transport, validation and callback routing |
Assets/control.html |
Minimal editing shell |
Assets/control-host.js |
Typed-wrapper bridge, state serialization and internal find UI |
Assets/control.css |
Control-only layout |
Assets are embedded resources under CODE.Editor.WinForms.Assets.*. The sample Form is a consumer, not the control implementation. Keep native API names, bridge operations, and JavaScript contracts aligned when adding a capability.
Keeping the two deliveries synchronized
The full project contains the canonical browser files at its root and a copy embedded in the WinForms class library. The standalone delivery contains the class library's embedded assets and corresponding tests. A runtime change must be propagated to both distributions before packing.
Recommended release sequence:
- Change the appropriate core/component source and targeted tests.
- Synchronize embedded assets and the two class-library copies.
- Run model tests and relevant browser/native acceptance checks.
- Rebuild the self-contained demo.
- Build and pack the WinForms library and test a package-only consumer.
- Update API/default/limitation documentation and test evidence.
- Create archives without transient build directories or obsolete loose editor copies.
This documentation-only update does not change runtime behavior or bump the package version.
A later custom-rendered source surface
The model boundary helps a future Monaco-like surface: it can retain document operations, history, serialization and host APIs. It still requires a new input/caret/selection strategy, line layout and hit testing, viewport rendering, scroll metrics, composition support, accessibility integration, and mobile behavior. Existing offset mapping and tests become useful constraints, not an implementation of those capabilities.
Keep Source and Visual views free to use different adapters. A highly optimized source surface need not force a rich document editor to abandon native contenteditable. Introduce an explicit adapter contract before replacing either, and keep host selection/save semantics stable.