Problemstructured note editor

Session: Sign in to solve

Solution.txt

Structured Note Editor

Problem

Design StructuredNoteEditor, an in-memory editor that owns an ordered sequence of blocks, a cursor gap, and linear undo and redo history. Testcases call one editor sequentially from one thread.

Requirements

  • Empty start. A new editor has no blocks, cursor position 0, and no undo or redo history.
  • Valid blocks. Every stored block has a valid ID, one supported type, and valid text.
  • Stable identity. Block IDs are unique in the current document; insert creates an identity, replace and move preserve it, and remove deletes it.
  • Ordered edits. Insert uses a document position, remove and replace use block identity, and move uses a final document position.
  • Valid cursor. The cursor is a gap from 0 through the current block count, inclusive, and each primitive edit applies the cursor transition defined in the API.
  • Atomic batches. A batch evaluates its primitive operations in order against one working state and commits only when every operation succeeds.
  • Rejected edit isolation. A rejected direct edit or batch leaves the document, cursor, undo history, and redo history unchanged; a rejected batch reports its first failing operation.
  • Command boundaries. Each successful direct mutation is one undoable command, and each successful batch is one command regardless of its operation count or net effect.
  • State restoration. Undo and redo restore the complete document and cursor saved at command boundaries in last-in, first-out order.
  • Linear history. Every successful direct mutation or batch clears all redo history, including a successful no-op; rejected edits, snapshots, failed undo, and failed redo do not.
  • Detached values. Stored blocks and snapshots are detached copies, so later editor changes and caller changes to supplied or returned values cannot affect one another.
  • Instance isolation. Distinct editor instances share no document, value, or history state.

API

All names are case-sensitive. The same camelCase callable names and field names apply in every supported runtime.

Value types

TypeFieldsContract
Blockid: string, type: string, text: stringA block value. Equality compares all three strings exactly.
EditOperationOne exact shape from the operation tableA primitive mutation used only by applyBatch.
DocumentSnapshotblocks: Block[], cursorPosition: integer, canUndo: boolean, canRedo: booleanA detached ordered view of the current document and history availability.
EditorErrorcode: string, operationIndex: optional integerThe exception for a rejected edit. operationIndex is present only for an invalid batch element or a primitive failure inside a batch. Message text is not contractual.

EditOperation allows exactly these shapes. A field not shown for a shape is invalid.

typeOther required fieldsPrimitive behavior
insertindex: integer, block: BlockThe behavior of insertBlock.
removeblockId: stringThe behavior of removeBlock.
replaceblockId: string, block: BlockThe behavior of replaceBlock.
moveblockId: string, toIndex: integerThe behavior of moveBlock.
set_cursorposition: integerThe behavior of setCursor.

Public callables

SignatureReturnsBehavior
StructuredNoteEditor()Not applicableCreates the empty state.
insertBlock(index: integer, block: Block)voidInserts a copy before the block at index; index == blockCount appends. The index is measured before insertion.
removeBlock(blockId: string)voidRemoves the identified block.
replaceBlock(blockId: string, replacement: Block)voidReplaces only the target block's type and text with copied values. replacement.id must equal blockId.
moveBlock(blockId: string, toIndex: integer)voidMoves the target so its index after removal and reinsertion is toIndex, where 0 <= toIndex < blockCount before the move. Moving to the current index succeeds as a no-op.
setCursor(position: integer)voidSets the cursor gap, where 0 <= position <= blockCount. Setting the current position succeeds as a no-op.
applyBatch(operations: EditOperation[])voidApplies from 1 through 100 primitive operations. Nested batches, undo, redo, and snapshot operations are invalid.
undo()booleanReturns false without changing state when undo history is empty. Otherwise restores the latest command's before-state, moves that command to redo history, and returns true.
redo()booleanReturns false without changing state when redo history is empty. Otherwise restores the latest undone command's after-state, moves that command to undo history, and returns true.
snapshot()DocumentSnapshotReturns a detached snapshot without creating history or changing either history stack.

For cursor transitions, use the cursor value at the current primitive step.

PrimitiveCursor transition
Insert at indexIncrement by one only when index < cursorPosition. Insertion at the cursor leaves the cursor before the inserted block.
Remove at old index iDecrement by one only when i < cursorPosition.
ReplaceDo not change the cursor.
Move from i to toIndexIf the indices match, do not change the cursor. Otherwise apply the remove rule, then the insert rule with toIndex and the cursor after removal.
Set cursorReplace the cursor with position.

Every rejected mutation raises EditorError with one of these codes.

CodeCondition
INVALID_BLOCKA block is absent or malformed, its ID is invalid, its type is unsupported, its text is absent, or its text exceeds the byte limit.
INVALID_BLOCK_IDA standalone blockId is absent or does not match the ID format.
INVALID_INDEXAn index or cursor position is outside its valid range for the state at that step.
DUPLICATE_BLOCK_IDAn insertion uses an ID already present in the working document.
BLOCK_ID_MISMATCHA valid replacement block has an ID different from blockId.
BLOCK_NOT_FOUNDA valid target ID is absent from the working document.
INVALID_BATCHThe operations array is absent, empty, or longer than 100.
INVALID_OPERATIONA batch element is absent, has an unsupported type, has a missing or unexpected field, or has a field of the wrong DTO type.

Validation has observable precedence:

  • insertBlock validates block, then the index range, then ID uniqueness.
  • removeBlock validates blockId, then requires the block to exist.
  • replaceBlock validates blockId, then replacement, then matching IDs, then target existence.
  • moveBlock validates blockId, then target existence, then toIndex against the current non-empty document.
  • setCursor validates position against the current document.
  • applyBatch validates its container first. It then processes elements from index 0, validating each exact operation shape before applying the corresponding precedence against the evolving working state. The first failure supplies the error code and zero-based operationIndex.

Wrong scalar types that a runtime's typed method signature cannot express are testcase or compile-time errors. Expressible absent DTO values follow the error table.

Examples

StepOperationResult
1snapshot()blocks=[], cursorPosition=0, canUndo=false, canRedo=false
2insertBlock(0, Block("a", "paragraph", "Alpha"))Blocks are [a]; cursor is 0.
3setCursor(1)Cursor is 1.
4insertBlock(0, Block("b", "heading", "Beta"))Blocks are [b, a]; cursor is 2.
5moveBlock("b", 1)Blocks are [a, b]; cursor is 1.

In the following sequence, shorthand names denote the exact EditOperation shapes above.

StepOperationResult
1applyBatch([remove("a"), insert(1, Block("c", "code", "x")), set_cursor(2)]) from blocks [a, b] and cursor 1Blocks are [b, c]; cursor is 2; one command is added.
2undo()Returns true; blocks are [a, b]; cursor is 1.
3redo()Returns true; blocks are [b, c]; cursor is 2.

Constraints

  • A block ID matches [A-Za-z0-9_-]{1,64}.
  • A block type is exactly heading, paragraph, code, or quote.
  • Block text may be empty and is at most 4096 bytes when encoded as UTF-8.
  • Text is preserved exactly, with no Unicode normalization, case folding, newline conversion, or whitespace trimming.
  • Testcase integers are within the signed 32-bit range, and valid live indices are at most 1000.
  • A testcase keeps at most 1000 live blocks, makes at most 10000 public calls, performs at most 10000 primitive mutations including batch elements, and retains at most 10000 successful history commands.
  • A batch contains from 1 through 100 operations.
  • snapshot may use time and space linear in the live document size. No tighter complexity bound applies to edits or history operations.

Notes

Concurrent calls on the same editor are outside scope. Methods perform no external I/O and do not wait, time out, support cancellation, or have shutdown behavior. Batch operations observe earlier successful primitive transitions in the same batch. Undo and redo restore saved command boundaries instead of recalculating cursor rules against later state.

PRIVATE WORKSPACE

Checking your session…

The statement is public. The editor, editorial, submissions, and saved work are private.