API / @aihu/editor

@aihu/editor

Authoring & UI

Hand-rolled, dependency-free, GX-governed rich-text editor — JSON doc model, invertible transactions, markdown (web-v1 dialect) round-trip, contenteditable view with IME-safe read-back, agent read/suggest/write surface.

version
0.1.0
exports
65
values
39
types
26
01

activeState

function
function activeState(core: EditorCore, sel: SelectionState | null): ActiveState

Toolbar-facing state (spec §9.2).

02

applyStep

function
function applyStep(doc: DocNode, step: Step): string | null
03

blockElOf

function
function blockElOf(root: Element, node: Node): Element | null

Nearest ancestor (or self) carrying data-block-id, bounded by root.

04

canExecute

function
function canExecute( core: EditorCore, sel: SelectionState | null, cmd: Command, features?: FeaturesConfig, ): boolean
05

containerLength

function
function containerLength(node: InlineContainer): number

UTF-16 code-unit length of a container's flattened text (A3).

06

containerText

function
function containerText(node: InlineContainer): string
07

diffToSteps

function
function diffToSteps(doc: DocNode, blockId: string, domText: string): Step[]

Flat single-container char diff (common prefix/suffix) → minimal steps.

08

docEqualsIgnoringIds

function
function docEqualsIgnoringIds(a: DocNode, b: DocNode): boolean

Structural doc equality modulo ids (round-trip acceptance A4).

09

emptyDoc

function
function emptyDoc(): DocNode
10

executeCommand

function
function executeCommand( core: EditorCore, sel: SelectionState | null, cmd: Command, features?: FeaturesConfig, origin: Origin = 'user.command', ): boolean

Execute a command against the resolved selection.

11

findContainer

function
function findContainer(doc: DocNode, id: string): Located | null

Find any node (block, listItem) by id.

12

freshId

function
function freshId(): string
13

fromMarkdown

function
function fromMarkdown(src: string): DocNode

Markdown → DocNode.

14

inlineContainers

function
function inlineContainers(doc: DocNode): InlineContainer[]

Document-ordered list of inline containers (listItems expanded in place).

15

invertStep

function
function invertStep(step: Step, docBefore: DocNode): Step
16

mapPoint

function
function mapPoint(step: Step, p: Point): Point
17

markAt

function
function markAt(node: InlineContainer, offset: number): Mark | null

Mark of the code unit left of `offset` — what typed text inherits.

18

matchInputRules

function
function matchInputRules( doc: DocNode, blockId: string, caretOffset: number, trigger: string, features: FeaturesConfig, extraRules: InputRule[] = [], ): { rule: InputRule; steps: Step[]; caretAfter: Point } | null

Find the first matching rule for the text before the caret.

19

migrate

function
function migrate(doc: unknown): DocNode

Versioned loader (I7): v1 loaders reject unknown major versions, forward-migrate known ones.

20

normalizeRuns

function
function normalizeRuns(runs: TextNode[]): TextNode[]

Normalize runs (I3): merge adjacent equal-mark runs, drop empty runs.

21

parseInlineToRuns

function
function parseInlineToRuns(src: string): TextNode[]
22

plainTextToBlocks

function
function plainTextToBlocks(text: string): BlockNode[]

Plain-text paste: blank lines split paragraphs; single newlines too (no hard breaks).

23

readDomSelection

function
function readDomSelection(root: Element): SelectionState | null
24

reconcileSteps

function
function reconcileSteps(doc: DocNode, blockId: string, containerEl: Element): Step[]

Structure-aware reconciliation step for one container: null when the DOM already equals the model.

25

resolveFeatures

function
function resolveFeatures(f?: FeaturesConfig | null): FeaturesConfig
26

runsFromDom

function
function runsFromDom(containerEl: Element): TextNode[]

Container element → normalized runs (structure-aware read, A2).

27

safeHref

function
function safeHref(href: string): string | null

safeHref — the one link-safety contract (spec §6.1 step 4, threat T2).

28

sanitizeHtmlToBlocks

function
function sanitizeHtmlToBlocks(html: string, features?: FeaturesConfig): BlockNode[]

HTML string → BlockNode[] via the inert-DOMParser allowlist walk.

29

toDom

function
function toDom(root: Element, point: Point): { node: Node; offset: number } | null

Model Point → DOM (node, offset).

30

toJSON

functionagent
function toJSON(doc: DocNode): DocNode

Canonical JSON (spec §8.1): the DocNode itself with stable key order and no undefineds — what persistence and agents see.

31

toMarkdown

function
function toMarkdown(doc: DocNode): string
32

toModel

function
function toModel(root: Element, node: Node, offset: number): Point | null

DOM (node, offset) → model Point.

33

validateDoc

function
function validateDoc(doc: DocNode): string | null

Invariant check (spec §1.3) — run after every transaction; violation rejects the whole transaction.

34

writeDomSelection

function
function writeDomSelection(root: Element, state: SelectionState): boolean
35

builtinRules

const
const builtinRules: InputRule[]
36

defaultFeatures

const
const defaultFeatures: Required< Pick<FeaturesConfig, 'headings' | 'lists' | 'blockquote' | 'link'> > & FeaturesConfig
37

AgentGateway

classagent
class AgentGateway
38

EditorCore

class
class EditorCore
39

EditorView

class
class EditorView
40

ActiveState

interface
interface ActiveState {
  marks: Set<'strong' | 'em' | 'code' | 'link'>
  blockType: 'paragraph' | 'heading' | 'blockquote' | 'listItem' | null
  headingLevel: 1 | 2 | 3 | null
  listOrdered: boolean | null
  canUndo: boolean
  canRedo: boolean
}
41

AgentCallResult

interfaceagent
interface AgentCallResult {
  ok: boolean
  code?: string
  /** For suggest mode: the staged proposal id. */
  proposalId?: string
  result?: unknown
}
42

AgentProposal

interfaceagent
interface AgentProposal {
  id: string
  action: string
  origin: `agent:${string}`
  steps: Step[]
  createdAt: number
}
43

DocNode

interface
interface DocNode {
  schema: 'aihu-editor/doc'
  version: 1
  children: BlockNode[]
}
44

DocOutlineEntry

interface
interface DocOutlineEntry {
  id: string
  type: BlockNode['type']
  level?: number
  text: string
}
45

EditorViewOptions

interface
interface EditorViewOptions {
  features?: FeaturesConfig
  readonly?: boolean
  inputRules?: InputRule[]
}
46

FeaturesConfig

interface
interface FeaturesConfig {
  headings?: boolean
  lists?: boolean
  blockquote?: boolean
  codeBlock?: boolean
  table?: boolean
  link?: boolean
  inputRules?: boolean | { disable?: string[] }
}

FeaturesConfig (spec §9.1): off ⇒ commands rejected, paste content degrades to text, input rules pruned.

47

HeadingAttrs

interface
interface HeadingAttrs {
  level: 1 | 2 | 3
}
48

InputRule

interface
interface InputRule {
  id: string
  scope: 'blockStart' | 'inline'
  /** Char whose insertion arms the check ('\n' = Enter, checked pre-split). */
  trigger: string
  /** Applied to block text [0, caret); must end at the caret. */
  match: RegExp
  apply(m: RegExpExecArray, ctx: InputRuleContext): Step[]
}
49

InputRuleContext

interface
interface InputRuleContext {
  blockId: string
  caret: Point
  doc: DocNode
}
50

ListAttrs

interface
interface ListAttrs {
  ordered: boolean
}
51

ListItemNode

interface
interface ListItemNode {
  id: string
  type: 'listItem'
  content: TextNode[]
}
52

Point

interface
interface Point {
  block: string
  offset: number
}

A position: (inline-container id, UTF-16 code-unit offset into the flattened inline text — mark-transparent).

53

SelectionContext

interface
interface SelectionContext {
  selection: SelectionState | null
  /** Up to 80 code units either side of the caret/range, for grounding "insert here". */
  textAround: { before: string; after: string } | null
}
54

TextNode

interface
interface TextNode {
  text: string
  mark: Mark | null
}

A flat run of text carrying at most ONE mark (invariant I4 — dialect guard).

55

Transaction

interface
interface Transaction {
  id: string
  time: number
  origin: Origin
  steps: Step[]
  selectionAfter?: SelectionState
}
56

AgentAccess

typeagent
type AgentAccess = 'none' | 'read' | 'suggest' | 'write'
57

ApplyResult

type
type ApplyResult = { ok: true; tr: Transaction } | { ok: false; code: string; step?: Step }
58

BlockNode

type
type BlockNode =
  | { id: string; type: 'paragraph'; content: TextNode[] }
  | { id: string; type: 'heading'; attrs: { level: 1 | 2 | 3 }; content: TextNode[] }
  | { id: string; type: 'list'; attrs: { ordered: boolean }; children: ListItemNode[] }
  | { id: string; type: 'blockquote'; content: TextNode[] }
  | { id: string; type: 'hr' }
59

Command

type
type Command =
  | { type: 'toggleMark'; mark: 'strong' | 'em' | 'code' }
  | { type: 'setLink'; href: string | null }
  | { type: 'setBlockType'; block: 'paragraph' | 'blockquote' }
  | { type: 'setBlockType'; block: 'heading'; level: 1 | 2 | 3 }
  | { type: 'toggleList'; ordered: boolean }
  | { type: 'insertHr' }
  | { type: 'undo' }
  | { type: 'redo' }
60

Dispose

type
type Dispose = () => void
61

InlineContainer

type
type InlineContainer = Extract<BlockNode | ListItemNode, { content: TextNode[] }>

Any node that carries inline content and can be addressed by a Point.

62

Mark

type
type Mark =
  | { type: 'strong' }
  | { type: 'em' }
  | { type: 'code' }
  | { type: 'link'; attrs: { href: string } }
63

Origin

type
type Origin =
  | 'user.typing'
  | 'user.command'
  | 'user.paste'
  | 'inputrule'
  | 'history'
  | 'dom.readback'
  | 'load'
  | `agent:${string}`

Transaction origins (spec §2, amended per Phase-0: `dom.readback` is the MutationObserver recovery path's distinct origin — G3 attribution).

64

SelectionState

type
type SelectionState =
  | { type: 'caret'; at: Point }
  | { type: 'range'; anchor: Point; head: Point } // anchor = where drag started
  | { type: 'node'; block: string }
65

Step

type
type Step =
  | { t: 'insertText'; at: Point; text: string; mark: Mark | null }
  | { t: 'insertRuns'; at: Point; runs: TextNode[] }
  | { t: 'deleteRange'; from: Point; to: Point } // same container
  | { t: 'setMark'; from: Point; to: Point; mark: Mark | null }
  | { t: 'setRuns'; id: string; runs: TextNode[] } // whole-container run replace (inverse carrier)
  | {
      t: 'splitBlock'
      at: Point
      newId: string
      /** Tail container kind. Default: same type for paragraph/listItem; paragraph for heading/blockquote. */
      tail?: { type: 'paragraph' | 'heading' | 'blockquote' | 'listItem'; attrs?: HeadingAttrs }
    }
  | { t: 'mergeBlock'; first: string; second: string }
  | {
      t: 'insertBlock'
      after: string | null // null = start (of doc, or of `in` list)
      node: BlockNode | ListItemNode
      /** Parent list id when inserting a listItem at list start (`after: null`). */
      in?: string
    }
  | { t: 'removeBlock'; id: string }
  | {
      t: 'setBlockType'
      id: string
      type: 'paragraph' | 'heading' | 'blockquote' | 'list'
      attrs?: HeadingAttrs | ListAttrs
      /** New wrapper-list id for the `→ list` conversion (the container keeps its id as the listItem). */
      newId?: string
    }
  | { t: 'setAttrs'; id: string; attrs: HeadingAttrs | ListAttrs }

Steps — the serializable, invertible mutation primitives (spec §2).