Architecture
This page is a reading guide for ARCHITECTURE.md. The full normative spec (pseudocode, acceptance checklist, migration history) lives in the repository root.
Layer Overview
flowchart TB
subgraph shell [Shell — Vue Components]
YanivEditor
EditorShell
end
subgraph runtime [Runtime — Pure Derivation]
Profile[resolveEditorProfile]
Chrome[resolveChromePolicy]
SessionKey[computeSessionKey]
end
subgraph session [Session — Tiptap Lifecycle]
useEditorSession
ContentAdapter
end
subgraph registry [Capability Registry]
CAPABILITIES
buildExtensions
end
YanivEditor --> EditorShell
EditorShell --> Profile
Profile --> useEditorSession
CAPABILITIES --> buildExtensions
buildExtensions --> useEditorSession| Layer | Responsibility | Forbidden |
|---|---|---|
| Shell | Layout, slots, expose, BlockMenuHost | Direct initEditor, scattered watches |
| Runtime | props → profile / chromePolicy / gates | Operating Tiptap instances |
| Session | sessionKey rebuild, phase, controlled content | UI visibility logic |
| Registry | Capability → extension + toolbar mapping | Import NodeView from components |
Configuration Model
Four axes merge into EditorRuntimeProfile:
- Phase —
mode: edit | preview - Preset —
basic | full | notion→ default features + layout - Appearance —
appearance+colorMode - Overrides —
features(explicit true/false; undefined inherits preset)
mergeFeatures is the single merge entry point.
ChromePolicy
resolveChromePolicy(profile, layout, gates) determines chrome visibility. Shell reads policy only—no mode === 'preview' checks in templates.
In preview: showEditChrome=false; header/footer/block menu/context bars hidden; extension set unchanged.
Outline expanded state is held by provideOutlinePanel and is not in chromePolicy. Initial state comes from the defaultOutlineExpanded prop (default false since v0.1.1).
Session and sessionKey
Triggers rebuild: extension gates, locale, inline toolbar signature, Inline placeholder / extraExtensions, schema-related options.
Does not trigger rebuild: phase, appearance, colorMode, upload/gallery/aiConfig, defaultOutlineExpanded, zIndexBase, and other integration props.
Rebuild flow: snapshot content → destroy → loading skeleton → async buildExtensions → prepareEditorContent (schema-adapt) → create Editor.
When the schema narrows (e.g. table off), JSON snapshots may contain unknown nodes. ContentAdapter.adaptJsonToSchema lifts unknown-node children and strips unknown marks before new Editor / setContent, avoiding TipTap Unknown node type.
Phase Transition
Unified entry requestPhaseTransition:
- edit → preview: emit cleanup first, then
setEditable(false) - preview → edit:
setEditable(true)first, then emit
ContentAdapter uses raw transactions + BYPASS_GUARD_META; do not use commands.setContent. Writes are schema-aware: HTML via DOMParser; JSON via adaptJsonToSchema.
Capability Registry
src/capabilities/registry.ts is the single source of truth. buildExtensions(host, ctx) serves both Full and Inline.
Extension tiers:
| Tier | Examples | Phase behavior |
|---|---|---|
| core | StarterKit, Link | always |
| content | Image, Table, AI | still shown in preview |
| interaction | DragHandle, Slash | editable guards |
| auxiliary | SearchReplace | clear state on phase switch |
| chromeCoupled | Outline | DOM late-binding |
Provide Tree
Core context is mounted at EditorShell root (not unmounted in preview):
provideEditorRuntimeprovideYanivEditorprovideEditorRoot/provideOverlayPortal(z-index tokens and overlay mounting)provideEditorLocaleprovideBlockMenuHostprovideOutlinePanel
Z-Index and Overlays
- Z-index tokens are scoped to
.yaniv-editor(variables.css); base--ye-z-basedefaults to1000, set viazIndexBaseprop on the root. EditorShellrenders.yaniv-editor__overlay-portalinside the root; bubble menus, BlockPicker, mention, AI popover, Ant Design overlays, custom Toast/Notice, etc. mount there—not ondocument.body(except drag preview / hidden file inputs). Do not use Ant Design staticmessage/notification.- See Z-Index & Overlays.
Architecture Invariants (Summary)
- DOM attributes are declarative bindings only (
data-phase,data-color-mode) - Only sessionKey triggers rebuild
- Chrome visibility reads chromePolicy only
- Each instance has independent locale / appearance
- Extensions must not call global
t()—usectx.locale - AI config uses getters; do not capture static values at configure time
- Overlays mount to overlay portal; z-index reads from editor root—no
bodymount or global fallback
See root ARCHITECTURE.md for all invariants.
Directory Structure
src/core/runtime/ profile, chrome, sessionKey
src/core/session/ useEditorSession, ContentAdapter
src/core/shell/ EditorShell, chrome subcomponents
src/capabilities/ registry, buildExtensions
src/extensions/ Tiptap extension implementations
src/components/ toolbar and UI controlsSee Project Structure for details.
Testing
Runtime pure functions and session behavior have vitest coverage: runtime.test.ts, contentAdapter.test.ts, useEditorSession.test.ts.
Run: pnpm test