Documentation
¶
Overview ¶
Package document defines the CalcMark document structure with blocks.
A CalcMark document consists of blocks: - CalcBlock: 1+ consecutive calculation lines (like Jupyter code cell) - TextBlock: markdown text (like Jupyter markdown cell)
Block boundaries: - 2 consecutive empty lines (\n\n\n) = hard boundary between blocks - 1 empty line (\n\n) = soft boundary (stays within current block)
Package document provides the CalcMark document model with blocks.
This package implements a higher-level document abstraction that manages mixed markdown and calculation content as a structured collection of blocks.
Architecture ¶
Key components:
- Block types (CalcBlock, TextBlock)
- Document structure with block management
- Dependency tracking between calc blocks
- Incremental evaluation for editors
Block Types ¶
CalcBlock: calculation code
block := &CalcBlock{}
block.AppendLine("x = 5")
block.AppendLine("y = x + 3")
TextBlock: markdown or prose
block := &TextBlock{}
block.AppendLine("# My Document")
block.AppendLine("Some text here")
Document Model ¶
Documents manage blocks in order:
doc := document.NewDocument()
calcBlock := &CalcBlock{}
calcBlock.AppendLine("total = 100")
doc.AppendBlock(calcBlock)
textBlock := &TextBlock{}
textBlock.AppendLine("# Results")
doc.AppendBlock(textBlock)
Incremental Evaluation ¶
The document model supports incremental updates for interactive editors:
// Initial evaluation
doc.Evaluate()
// User edits a block
doc.ReplaceBlockSource(blockID, []string{"total = 200"})
// Re-evaluate only affected blocks
doc.EvaluateBlock(blockID)
Dependency Tracking ¶
The document tracks dependencies between blocks to enable smart re-evaluation. When a block changes, only dependent blocks are re-evaluated.
Line Detection ¶
Automatic detection of calculation vs markdown:
detector := document.NewDetector()
blockType := detector.DetectLine("x = 5") // CalcBlock
blockType = detector.DetectLine("# Title") // TextBlock
Use Cases ¶
This package is designed for:
- Interactive markdown editors with live calculation
- Document processing pipelines
- Static site generators with embedded calculations
- Notebook-style interfaces
Performance ¶
Document operations are optimized for incremental updates. Evaluation complexity is O(affected blocks) not O(total blocks).
Package document provides document structure and parsing for CalcMark.
Package document provides document structure and parsing for CalcMark.
Package document provides document structure and parsing for CalcMark.
Index ¶
- func ExchangeRateKey(from, to string) string
- func ExtractStatementReferences(node ast.Node) []string
- func ParseExchangeRateKey(key string) (from, to string, err error)
- type Block
- type BlockNode
- type BlockType
- type CalcBlock
- func (cb *CalcBlock) AddDiagnostic(diag Diagnostic)
- func (cb *CalcBlock) ClearDiagnostics()
- func (cb *CalcBlock) ConvertApplied() []bool
- func (cb *CalcBlock) Dependencies() []string
- func (cb *CalcBlock) Diagnostics() []Diagnostic
- func (cb *CalcBlock) Error() error
- func (cb *CalcBlock) IsDirty() bool
- func (cb *CalcBlock) LastValue() types.Type
- func (cb *CalcBlock) Results() []types.Type
- func (cb *CalcBlock) ScaleExempt() []bool
- func (cb *CalcBlock) SetConvertApplied(applied []bool)
- func (cb *CalcBlock) SetDependencies(deps []string)
- func (cb *CalcBlock) SetDiagnostics(diags []Diagnostic)
- func (cb *CalcBlock) SetDirty(dirty bool)
- func (cb *CalcBlock) SetError(err error)
- func (cb *CalcBlock) SetLastValue(val types.Type)
- func (cb *CalcBlock) SetResults(results []types.Type)
- func (cb *CalcBlock) SetScaleExempt(exempt []bool)
- func (cb *CalcBlock) SetStatements(stmts []ast.Node)
- func (cb *CalcBlock) SetVariables(vars []string)
- func (cb *CalcBlock) Source() []string
- func (cb *CalcBlock) Statements() []ast.Node
- func (cb *CalcBlock) Type() BlockType
- func (cb *CalcBlock) Variables() []string
- type ConvertToConfig
- type DependencyAnalyzer
- type Detector
- type Diagnostic
- type Document
- func (d *Document) ApplyFrontmatter(env EnvironmentWriter) error
- func (d *Document) DeleteBlock(blockID string) (*UpdateResult, error)
- func (d *Document) EnsureFrontmatter() *Frontmatter
- func (d *Document) GetBlock(id string) (*BlockNode, bool)
- func (d *Document) GetBlocks() []*BlockNode
- func (d *Document) GetBlocksInDependencyOrder(blockIDs []string) []string
- func (d *Document) GetFrontmatter() *Frontmatter
- func (d *Document) GetTransitiveDependents(changedVars []string) []string
- func (d *Document) InsertBlock(afterBlockID string, blockType BlockType, source []string) (*UpdateResult, error)
- func (d *Document) ReplaceBlockSource(blockID string, newSource []string) (*UpdateResult, error)
- func (d *Document) SetFrontmatter(fm *Frontmatter)
- type EnvironmentWriter
- type ExtraField
- type FiscalYearConfig
- type Frontmatter
- func (f *Frontmatter) ExchangeKeys() []string
- func (f *Frontmatter) GetExchangeRate(from, to string) (decimal.Decimal, bool)
- func (f *Frontmatter) GlobalKeys() []string
- func (f *Frontmatter) HasExchangeRate(key string) bool
- func (f *Frontmatter) HasGlobal(name string) bool
- func (f *Frontmatter) HasGlobals() bool
- func (f *Frontmatter) HasScale() bool
- func (f *Frontmatter) LineCount() int
- func (f *Frontmatter) Serialize() string
- func (f *Frontmatter) SetExchangeRate(key string, rate decimal.Decimal)
- func (f *Frontmatter) SetGlobal(name, valueExpr string)
- func (f *Frontmatter) SetRawSource(raw string)
- type MeasurementFrontmatter
- type ParsedGlobals
- type ScaleConfig
- type TextBlock
- func (tb *TextBlock) AddDiagnostic(diag Diagnostic)
- func (tb *TextBlock) ClearDiagnostics()
- func (tb *TextBlock) ClearInterpolatedSource()
- func (tb *TextBlock) Diagnostics() []Diagnostic
- func (tb *TextBlock) HTML() string
- func (tb *TextBlock) InterpolatedHTMLSourceText() string
- func (tb *TextBlock) InterpolatedSource() []string
- func (tb *TextBlock) InterpolatedSourceText() string
- func (tb *TextBlock) IsDirty() bool
- func (tb *TextBlock) Render() string
- func (tb *TextBlock) SetDirty(dirty bool)
- func (tb *TextBlock) SetHTML(html string)
- func (tb *TextBlock) SetInterpolatedHTMLSource(lines []string)
- func (tb *TextBlock) SetInterpolatedSource(lines []string)
- func (tb *TextBlock) Source() []string
- func (tb *TextBlock) SourceText() string
- func (tb *TextBlock) Type() BlockType
- type UpdateResult
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExchangeRateKey ¶
ExchangeRateKey creates a normalized key for looking up exchange rates. Format: "FROM_TO" (e.g., "USD_EUR").
func ExtractStatementReferences ¶
ExtractStatementReferences returns the sorted set of variable names referenced by a single AST statement. For assignments, only RHS references are included (the defined variable is excluded by extractIdentifiers). Returns nil for a nil node. O(n) time where n is the number of AST nodes in the statement.
func ParseExchangeRateKey ¶
ParseExchangeRateKey splits a key like "USD_EUR" into (from, to) parts. Returns an error if the key format is invalid. Currency codes must be exactly 3 ASCII letters (ISO 4217 format, e.g., USD, EUR, GBP).
Types ¶
type Block ¶
type Block interface {
// Type returns the block type.
Type() BlockType
// Source returns the raw source lines.
Source() []string
// IsDirty returns true if the block needs re-evaluation/rendering.
IsDirty() bool
// SetDirty marks the block as needing update.
SetDirty(bool)
}
Block represents a content block in a CalcMark document.
type BlockNode ¶
type BlockNode struct {
ID string // UUID (session-ephemeral)
Block Block // Underlying block (CalcBlock or TextBlock)
}
BlockNode wraps a Block with metadata for incremental updates.
type CalcBlock ¶
type CalcBlock struct {
// contains filtered or unexported fields
}
CalcBlock represents one or more consecutive calculation lines. Like a Jupyter code cell.
func NewCalcBlock ¶
NewCalcBlock creates a new calculation block.
func (*CalcBlock) AddDiagnostic ¶
func (cb *CalcBlock) AddDiagnostic(diag Diagnostic)
AddDiagnostic adds a single diagnostic to this block.
func (*CalcBlock) ClearDiagnostics ¶
func (cb *CalcBlock) ClearDiagnostics()
ClearDiagnostics removes all diagnostics from this block.
func (*CalcBlock) ConvertApplied ¶ added in v1.8.0
ConvertApplied returns the per-statement conversion flags. A true value at index i means convert_to actually changed the unit for statement i (detected by comparing units before and after Apply).
func (*CalcBlock) Dependencies ¶
Dependencies returns variables referenced from other blocks.
func (*CalcBlock) Diagnostics ¶
func (cb *CalcBlock) Diagnostics() []Diagnostic
Diagnostics returns structured errors/warnings with position info.
func (*CalcBlock) ScaleExempt ¶ added in v1.6.2
ScaleExempt returns the per-statement scale exemption flags. A true value at index i means statement i references @scale and should not be scaled (to prevent double-scaling).
func (*CalcBlock) SetConvertApplied ¶ added in v1.8.0
SetConvertApplied sets the per-statement conversion flags.
func (*CalcBlock) SetDependencies ¶
SetDependencies sets the dependencies.
func (*CalcBlock) SetDiagnostics ¶
func (cb *CalcBlock) SetDiagnostics(diags []Diagnostic)
SetDiagnostics sets the structured diagnostics for this block.
func (*CalcBlock) SetLastValue ¶
SetLastValue sets the last evaluated value.
func (*CalcBlock) SetResults ¶
SetResults sets the per-statement evaluation results.
func (*CalcBlock) SetScaleExempt ¶ added in v1.6.2
SetScaleExempt sets the per-statement scale exemption flags.
func (*CalcBlock) SetStatements ¶
SetStatements sets the parsed AST nodes.
func (*CalcBlock) SetVariables ¶
SetVariables sets the variables defined in this block.
func (*CalcBlock) Statements ¶
Statements returns the parsed AST nodes.
type ConvertToConfig ¶ added in v1.6.2
type ConvertToConfig struct {
System string // "si" or "imperial"
UnitCategories []string // empty = all
}
ConvertToConfig configures the document-level unit system conversion. When present, eligible quantities are converted to the target measurement system. UnitCategories restricts conversion to specific categories (empty = all).
type DependencyAnalyzer ¶
type DependencyAnalyzer struct{}
DependencyAnalyzer extracts variable dependencies from CalcBlocks.
func NewDependencyAnalyzer ¶
func NewDependencyAnalyzer() *DependencyAnalyzer
NewDependencyAnalyzer creates a new dependency analyzer.
func (*DependencyAnalyzer) AnalyzeBlock ¶
func (da *DependencyAnalyzer) AnalyzeBlock(block *CalcBlock) error
AnalyzeBlock parses a CalcBlock and extracts: - Variables defined (from assignments) - Variables referenced (from expressions)
type Detector ¶
type Detector struct {
// contains filtered or unexported fields
}
Detector analyzes source text and splits it into blocks.
func (*Detector) DetectBlocks ¶
DetectBlocks splits source into blocks using these rules: - 2 consecutive empty lines = block boundary - 1 empty line = part of current block - Calculations vs text determined by parsing each line
Unicode-aware: handles all line terminators (LF, CRLF, CR, U+2028, U+2029).
func (*Detector) IsCalculation ¶
IsCalculation checks if a line is a valid calculation. The approach: if a line parses successfully as a calculation, it's a calculation. If it fails to parse, it's text (markdown).
Returns (true, nil) for valid calculation lines. Returns (false, nil) for text lines (including invalid syntax - treated as markdown).
This is the public API for determining line type. Used by the TUI to decide how to render lines in the preview pane.
type Diagnostic ¶
type Diagnostic struct {
BlockID string
Severity string // "error", "warning", "hint"
Code string
Message string
Detailed string // Detailed explanation with context and guidance (from semantic checker)
Line int // 1-indexed line number within the block (for block-internal lookups)
Column int // 1-indexed column number
DocLine int // 1-indexed document-absolute line number (0 if unknown)
EndLine int // 1-indexed end line (0 if unknown — falls back to start line)
EndColumn int // 1-indexed end column (0 if unknown — falls back to end-of-line)
}
Diagnostic represents a validation issue with source position info.
type Document ¶
type Document struct {
// contains filtered or unexported fields
}
Document represents a CalcMark document with incremental evaluation. Tracks blocks, dependencies, and provides efficient change propagation.
Note: Document is a pure data structure and does NOT contain interpreter state. The impl/document.Evaluator is responsible for evaluation with its own environment.
func NewDocument ¶
NewDocument creates a new document from CalcMark source and eagerly parses it.
func (*Document) ApplyFrontmatter ¶
func (d *Document) ApplyFrontmatter(env EnvironmentWriter) error
ApplyFrontmatter injects frontmatter values (exchange rates, globals) into the given environment. This should be called before evaluation. The env parameter must implement EnvironmentWriter (e.g., *interpreter.Environment).
func (*Document) DeleteBlock ¶
func (d *Document) DeleteBlock(blockID string) (*UpdateResult, error)
DeleteBlock removes a block and updates dependents.
func (*Document) EnsureFrontmatter ¶
func (d *Document) EnsureFrontmatter() *Frontmatter
EnsureFrontmatter returns the frontmatter, creating an empty one if nil.
func (*Document) GetBlocksInDependencyOrder ¶
GetBlocksInDependencyOrder returns block IDs sorted so that dependencies come before dependents. This is a topological sort based on the dependency graph. Useful for evaluating blocks in the correct order after a change.
func (*Document) GetFrontmatter ¶
func (d *Document) GetFrontmatter() *Frontmatter
GetFrontmatter returns the document's frontmatter (may be nil).
func (*Document) GetTransitiveDependents ¶
GetTransitiveDependents returns all block IDs that transitively depend on any of the given variables. This follows the dependency chain: if a→b→c, changing 'a' returns blocks containing 'b' AND 'c'.
This is the key API for reactive UIs: when a variable changes, call this to get the minimal set of blocks that need re-evaluation.
func (*Document) InsertBlock ¶
func (d *Document) InsertBlock(afterBlockID string, blockType BlockType, source []string) (*UpdateResult, error)
InsertBlock inserts a new block after the specified block ID.
func (*Document) ReplaceBlockSource ¶
func (d *Document) ReplaceBlockSource(blockID string, newSource []string) (*UpdateResult, error)
ReplaceBlockSource replaces the source of a block and propagates changes. Returns only the affected block IDs for efficient UI updates.
func (*Document) SetFrontmatter ¶
func (d *Document) SetFrontmatter(fm *Frontmatter)
SetFrontmatter replaces the document's frontmatter.
type EnvironmentWriter ¶
type EnvironmentWriter interface {
Set(name string, value types.Type)
SetExchangeRate(from, to string, rate decimal.Decimal)
}
EnvironmentWriter can receive frontmatter values during evaluation. This interface is implemented by impl/interpreter.Environment. It decouples the spec/document package from the impl/interpreter package.
type ExtraField ¶ added in v1.8.8
ExtraField holds a non-CalcMark frontmatter key-value pair.
type FiscalYearConfig ¶ added in v1.11.0
type FiscalYearConfig struct {
Month int // 1-12 (January-December)
Day int // 1-31 (defaults to 1 if not specified)
}
FiscalYearConfig holds the fiscal year start month and optional day.
type Frontmatter ¶
type Frontmatter struct {
// Exchange contains currency exchange rates as "FROM_TO" -> rate.
// Example: "USD_EUR" -> 0.92 means 1 USD = 0.92 EUR
Exchange map[string]decimal.Decimal
// Globals contains user-defined variables as name -> expression string.
// Values are CalcMark expressions that will be parsed and evaluated.
// Example: "base_date" -> "Jan 15 2025", "tax_rate" -> "0.32"
Globals map[string]string
// Scale is the document-level scale transform (nil if not present).
Scale *ScaleConfig
// ConvertTo is the document-level unit system conversion (nil if not present).
ConvertTo *ConvertToConfig
// Measurement configures how ambiguous unit names are interpreted (nil if not present).
Measurement *MeasurementFrontmatter
// FiscalYearStarts configures the start of the fiscal year (nil if not set).
// When set, fiscal expressions (FQ1, FY26, "this fiscal quarter") resolve relative
// to this configuration. When nil, fiscal expressions produce an error diagnostic.
FiscalYearStarts *FiscalYearConfig
// Extra contains non-CalcMark frontmatter keys (e.g., title, author)
// preserved in document order. Values are the raw YAML-parsed types
// (string, int, float64, []any, map[string]any).
Extra []ExtraField
// contains filtered or unexported fields
}
Frontmatter represents structured metadata at the start of a CalcMark document. It is delimited by --- markers and contains YAML content.
Reserved keys (CalcMark grammar):
- exchange: Currency conversion rates
- scale: Document-level quantity scaling
- convert_to: Document-level unit system conversion
User-defined variables go under 'globals':
--- exchange: USD_EUR: 0.92 scale: 4 convert_to: imperial globals: base_date: Jan 15 2025 tax_rate: 0.32 ---
func ParseFrontmatter ¶
func ParseFrontmatter(source string) (*Frontmatter, string, error)
ParseFrontmatter extracts YAML frontmatter from the beginning of a document. Returns the parsed frontmatter, the remaining source (without frontmatter), and any error.
Frontmatter must:
- Start at line 1 with exactly "---"
- End with a line containing exactly "---"
- Contain valid YAML between the delimiters
- Unknown top-level keys are silently ignored; only CalcMark keys are processed (exchange, globals, scale, convert_to)
If no frontmatter is present, returns (nil, source, nil).
func (*Frontmatter) ExchangeKeys ¶
func (f *Frontmatter) ExchangeKeys() []string
ExchangeKeys returns exchange rate keys in insertion order. Falls back to sorted keys for backward compatibility when frontmatter is created via struct literals rather than ParseFrontmatter or SetExchangeRate.
func (*Frontmatter) GetExchangeRate ¶
func (f *Frontmatter) GetExchangeRate(from, to string) (decimal.Decimal, bool)
GetExchangeRate looks up the rate to convert from one currency to another. Returns the rate and true if found, or zero and false if not defined.
func (*Frontmatter) GlobalKeys ¶
func (f *Frontmatter) GlobalKeys() []string
GlobalKeys returns global variable names in insertion order. Falls back to sorted keys for backward compatibility when frontmatter is created via struct literals rather than ParseFrontmatter or SetGlobal.
func (*Frontmatter) HasExchangeRate ¶
func (f *Frontmatter) HasExchangeRate(key string) bool
HasExchangeRate returns true if the exchange rate is defined in frontmatter.
func (*Frontmatter) HasGlobal ¶
func (f *Frontmatter) HasGlobal(name string) bool
HasGlobal returns true if the global variable is defined in frontmatter.
func (*Frontmatter) HasGlobals ¶ added in v1.6.2
func (f *Frontmatter) HasGlobals() bool
HasGlobals returns true if any globals are defined in frontmatter.
func (*Frontmatter) HasScale ¶ added in v1.6.2
func (f *Frontmatter) HasScale() bool
HasScale returns true if a scale directive is defined in frontmatter.
func (*Frontmatter) LineCount ¶ added in v1.8.0
func (f *Frontmatter) LineCount() int
LineCount returns the number of source lines occupied by the frontmatter (including both --- delimiters). Returns 0 if no frontmatter is present.
func (*Frontmatter) Serialize ¶
func (f *Frontmatter) Serialize() string
Serialize returns the frontmatter as a YAML string with --- delimiters. If the frontmatter has no content (no exchange rates, no globals), returns "". When rawSource is available (from parsing), the original text is preserved to support natural editing (Enter, whitespace, etc.) in the TUI editor.
func (*Frontmatter) SetExchangeRate ¶
func (f *Frontmatter) SetExchangeRate(key string, rate decimal.Decimal)
SetExchangeRate sets an exchange rate. The key should be in FROM_TO format.
func (*Frontmatter) SetGlobal ¶
func (f *Frontmatter) SetGlobal(name, valueExpr string)
SetGlobal sets a global variable value. The valueExpr is stored as the raw expression string for serialization.
func (*Frontmatter) SetRawSource ¶
func (f *Frontmatter) SetRawSource(raw string)
SetRawSource replaces the serialized frontmatter text (including --- delimiters and trailing newline) without re-parsing YAML. This allows the TUI editor to preserve user-typed text even when intermediate edits produce invalid YAML. Parsed data (Globals, Exchange) remains unchanged until a valid parse succeeds.
type MeasurementFrontmatter ¶ added in v1.8.4
type MeasurementFrontmatter struct {
units.MeasurementConfig
// Strict controls whether the formatter annotates bare ambiguous units in output.
// nil = use config default (true), true = annotate, false = pass through.
Strict *bool
}
MeasurementFrontmatter wraps units.MeasurementConfig with an optional Strict flag that controls formatter annotation of bare ambiguous units.
type ParsedGlobals ¶
ParsedGlobals contains parsed global variable values ready for injection into the interpreter environment.
func ParseGlobals ¶
func ParseGlobals(rawGlobals map[string]string) (*ParsedGlobals, error)
ParseGlobals parses the raw string values from frontmatter into typed CalcMark values. Only literal values are allowed (no expressions or calculations):
- Numbers: 42, 3.14, 1.5K, 25%
- Quantities: 10 meters, 5 kg, 100 MB
- Currencies: $100, 50 EUR
- Dates: Jan 15 2025, today
- Durations: 5 days, 2 weeks
- Booleans: true, false
- Rates: 100 MB/s, $50/hour
Returns an error if any value cannot be parsed or is not a literal.
func ParseGlobalsOrdered ¶
func ParseGlobalsOrdered(rawGlobals map[string]string, keys []string) (*ParsedGlobals, error)
ParseGlobalsOrdered parses globals in the specified key order. This ensures variables are processed in document order (frontmatter order).
type ScaleConfig ¶ added in v1.6.2
type ScaleConfig struct {
Factor decimal.Decimal
UnitCategories []string // empty = nothing scales
}
ScaleConfig configures the document-level scale transform. When present, eligible quantities are multiplied by Factor. UnitCategories lists the categories to scale (empty = nothing scales).
type TextBlock ¶
type TextBlock struct {
// contains filtered or unexported fields
}
TextBlock represents markdown text. Like a Jupyter markdown cell.
func NewTextBlock ¶
NewTextBlock creates a new text block.
func (*TextBlock) AddDiagnostic ¶ added in v1.9.9
func (tb *TextBlock) AddDiagnostic(diag Diagnostic)
AddDiagnostic adds a single diagnostic to this text block.
func (*TextBlock) ClearDiagnostics ¶ added in v1.9.9
func (tb *TextBlock) ClearDiagnostics()
ClearDiagnostics removes all diagnostics from this text block.
func (*TextBlock) ClearInterpolatedSource ¶ added in v1.8.0
func (tb *TextBlock) ClearInterpolatedSource()
ClearInterpolatedSource resets interpolation (e.g., when source changes).
func (*TextBlock) Diagnostics ¶ added in v1.9.9
func (tb *TextBlock) Diagnostics() []Diagnostic
Diagnostics returns warnings for this text block.
func (*TextBlock) InterpolatedHTMLSourceText ¶ added in v1.8.0
InterpolatedHTMLSourceText returns the sentinel-wrapped source for HTML rendering. Falls back to InterpolatedSourceText() when no HTML-specific source is set.
func (*TextBlock) InterpolatedSource ¶ added in v1.8.0
InterpolatedSource returns interpolated lines if set, otherwise raw source. All output consumers should call this instead of Source() for display.
func (*TextBlock) InterpolatedSourceText ¶ added in v1.8.0
InterpolatedSourceText returns interpolated source as a single string.
func (*TextBlock) Render ¶
Render converts the markdown source to HTML. This implementation is only available in native Go builds (not WASM).
func (*TextBlock) SetInterpolatedHTMLSource ¶ added in v1.8.0
SetInterpolatedHTMLSource stores sentinel-wrapped lines for HTML rendering.
func (*TextBlock) SetInterpolatedSource ¶ added in v1.8.0
SetInterpolatedSource stores the post-interpolation lines.
func (*TextBlock) SourceText ¶
SourceText returns source as a single string.
type UpdateResult ¶
type UpdateResult struct {
// ModifiedBlockID is the block that was directly modified
ModifiedBlockID string
// AffectedBlockIDs are all blocks that need UI updates
// (includes modified block + dependents)
AffectedBlockIDs []string
// Diagnostics from semantic validation
Diagnostics []Diagnostic
}
UpdateResult contains information about what changed in the document.