Typed editing types
Typed edit-transaction structures at the Rust boundary.
These are the same structures the core uses to accept edits, shown as they appear from the browser side. Two of their properties matter from the very first call.
Positions are measured in UTF-16 code units — the same way JavaScript counts them. So utf16_offset comes straight from the browser selection and needs no recalculation: a character outside the basic multilingual plane — an emoji, a rare CJK character — takes two units in both places.
An unknown field causes a refusal, not silence. The structures don’t accept extra fields: a typo in a name isn’t silently dropped — the core responds with a refusal. Keep this in mind before adding a field “just in case.”
Acceptance and projections of these same structures are on the shell pages, starting with window.fastdocShell · edit.
type RequestId = string;type DocumentId = string;type SessionId = string;type ViewId = string;type StoryId = string;type NodeId = string;type Revision = number;type SelectionRevision = number;type Utf16Offset = number;type Affinity = "upstream" | "downstream";
interface DocumentPosition { story_id: StoryId; node_id: NodeId; utf16_offset: Utf16Offset; affinity: Affinity;}interface DocumentRange { anchor: DocumentPosition; focus: DocumentPosition; }interface DocumentSelection { range: DocumentRange; }
interface CharacterProperties { font_family?: string | null; font_size_half_points?: number | null; bold?: boolean | null; italic?: boolean | null; underline?: "none" | "single" | "double" | "dotted" | "thick" | "dash" | "wave" | null; color_rgb?: string | null; vertical_alignment?: "baseline" | "superscript" | "subscript" | null; highlight?: {mode:"color";color:string} | {mode:"clear"} | null; shading?: {mode:"fill";color_rgb:string} | {mode:"clear"} | null; strikethrough?: boolean | null; text_effect?: "none" | "outline" | "shadow" | "emboss" | "imprint" | null; change_case?: "sentence" | "lower" | "upper" | "capitalize" | "toggle" | null;}type LineSpacing = | {rule:"auto";twips:number} | {rule:"exact";twips:number} | {rule:"at_least";twips:number};type ParagraphNumbering = | {action:"apply"|"continue";kind:"bullet"|"decimal";ilvl:number} | {action:"set_level";ilvl:number} | {action:"clear"};interface ParagraphProperties { alignment?: "left"|"center"|"right"|"justify" | null; left_indent_twips?: number | null; right_indent_twips?: number | null; first_line_indent_twips?: number | null; line_spacing?: LineSpacing | null; shading?: {mode:"fill";color_rgb:string}|{mode:"clear"}|null; borders?: unknown; numbering?: ParagraphNumbering | null; style?: "normal"|"heading1"|"heading2"|"heading3" | null;}interface FragmentRun { text:string; properties:CharacterProperties; }interface FragmentParagraph { runs:FragmentRun[]; properties:ParagraphProperties; }interface DocumentFragment { paragraphs:FragmentParagraph[]; }
type EditCommand = | {type:"insert_text";at:DocumentPosition;text:string} | {type:"insert_tracked_text";text:string;expected_model_hash:string} | {type:"delete_range";range:DocumentRange} | {type:"delete_tracked_deletion_range";paragraph:DocumentPosition;deletion_index:number;start_utf16_offset:number;end_utf16_offset:number} | {type:"replace_range";range:DocumentRange;text:string} | {type:"split_paragraph";at:DocumentPosition} | {type:"split_paragraph_replacing_range";range:DocumentRange} | {type:"merge_paragraphs";left:DocumentPosition;right:DocumentPosition} | {type:"paste_plain_text";range:DocumentRange;text:string} | {type:"paste_fragment";range:DocumentRange;fragment:DocumentFragment} | {type:"set_character_properties";range:DocumentRange;properties:CharacterProperties} | {type:"set_paragraph_properties";range:DocumentRange;properties:ParagraphProperties} | {type:"clear_formatting";range:DocumentRange} | {type:"autocorrect";range:DocumentRange;replacement:string} | {type:"insert_merge_field";field_name:string;expected_model_hash:string} | {type:"apply_structural_snapshot";transition_kind:string;selection_after?:DocumentSelection|null} | {type:"undo"} | {type:"redo"};interface EditRequest { request_id:RequestId; document_id:DocumentId; session_id:SessionId; view_id:ViewId; expected_revision:Revision; expected_selection_revision:SelectionRevision; command:EditCommand;}interface SelectionUpdateRequest { request_id:RequestId; document_id:DocumentId; session_id:SessionId; view_id:ViewId; expected_revision:Revision; expected_selection_revision:SelectionRevision; selection:DocumentSelection;}type CompositionCommand = | {type:"start"|"update";range:DocumentRange;text:string} | {type:"cancel"} | {type:"commit"};interface CompositionRequest { request_id:RequestId; document_id:DocumentId; session_id:SessionId; view_id:ViewId; expected_revision:Revision; expected_selection_revision:SelectionRevision; command:CompositionCommand;}interface SaveCheckpointTarget { path:string; expected_hash?:string|null; expected_identity?:Record<string,unknown>|null;}