Skip to content

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

mermaid
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
LayerResponsibilityForbidden
ShellLayout, slots, expose, BlockMenuHostDirect initEditor, scattered watches
Runtimeprops → profile / chromePolicy / gatesOperating Tiptap instances
SessionsessionKey rebuild, phase, controlled contentUI visibility logic
RegistryCapability → extension + toolbar mappingImport NodeView from components

Configuration Model

Four axes merge into EditorRuntimeProfile:

  • Phasemode: edit | preview
  • Presetbasic | full | notion → default features + layout
  • Appearanceappearance + colorMode
  • Overridesfeatures (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:

TierExamplesPhase behavior
coreStarterKit, Linkalways
contentImage, Table, AIstill shown in preview
interactionDragHandle, Slasheditable guards
auxiliarySearchReplaceclear state on phase switch
chromeCoupledOutlineDOM late-binding

Provide Tree

Core context is mounted at EditorShell root (not unmounted in preview):

  • provideEditorRuntime
  • provideYanivEditor
  • provideEditorRoot / provideOverlayPortal (z-index tokens and overlay mounting)
  • provideEditorLocale
  • provideBlockMenuHost
  • provideOutlinePanel

Z-Index and Overlays

  • Z-index tokens are scoped to .yaniv-editor (variables.css); base --ye-z-base defaults to 1000, set via zIndexBase prop on the root.
  • EditorShell renders .yaniv-editor__overlay-portal inside the root; bubble menus, BlockPicker, mention, AI popover, Ant Design overlays, custom Toast/Notice, etc. mount there—not on document.body (except drag preview / hidden file inputs). Do not use Ant Design static message / notification.
  • See Z-Index & Overlays.

Architecture Invariants (Summary)

  1. DOM attributes are declarative bindings only (data-phase, data-color-mode)
  2. Only sessionKey triggers rebuild
  3. Chrome visibility reads chromePolicy only
  4. Each instance has independent locale / appearance
  5. Extensions must not call global t()—use ctx.locale
  6. AI config uses getters; do not capture static values at configure time
  7. Overlays mount to overlay portal; z-index reads from editor root—no body mount 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 controls

See 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