v0.4.0
What's Changed
This release adds the portable reference and annotation layer from the 2026-07 TextRef design cycle plus cross-language logical word metrics, finalized under an independent senior release review (review and breaking-change catalog; PRs #18, #20, #21). Because it changes documented behavior on a pre-1.0 library, it bumps the minor version (per the pre-1.0 rule in docs/publishing.md). If you depend on an unbounded flexdoc>=0.3.0, review the breaking changes below or pin <0.4 before upgrading.
New Features
Native TextRef integration
Strict DocRef/TextRef values identify whole documents, spans, points, and semantic sections with canonical JSON and reversible textref:0.1 URIs. FlexDoc.references() binds a document locator to one source snapshot: it maps every locatable public value (for_target, for_span, for_point, for_section), resolves references with typed outcomes on independent document/source-validation/selector axes (resolution never silently picks a duplicate), retrieves structured line-window context, and renders deterministic annotation views for humans and LLMs. Span quote evidence is configurable per context or per span; compact exact-less spans stay bound to one source hash.
Consumer-owned annotations
TextAnnotation and the one-document AnnotationSet sidecar round-trip strict JSON and safe YAML, and every sidecar entry is validated through the complete TextRef evidence contract at parse time. DocGraph/v0.2 embeds a matching sidecar after verifying its document and source hash against the snapshot, and bounds-checks embedded positions. Committed JSON Schemas ship for both formats in the wheel.
Cross-language logical word metrics
TextUnit.words now measures normalized word-equivalent volume across natural language, CJK text, source code, URLs, and punctuation-dense content. TextUnit.raw_words and raw_word_count() keep literal whitespace counting, and logical_word_count() exposes the dependency-free primitive. See the logical-word definition and validation.
Breaking Changes
Made cleanly with no compatibility aliases, given the pre-1.0 status. Full catalog with rationales and migrations: review doc §4.
FlexDoc.graph()requires a document locator —doc.graph()becomesdoc.graph(document="path/or/id.md");build_doc_graph()and the debug helpersdoc_graph_yaml()/dump_views()take the same argument. Every serialized graph is now self-identifying.- DocGraph wire contract v0.1 → v0.2 —
schemais"DocGraph/v0.2";source.documentis required; the unqualifiedsource.sha256field is replaced by algorithm-qualifiedsource.source_hash(shared with TextRef); models are strict (unknown fields rejected, frozen, strict types) and validate node references, span/text consistency, and annotation bounds. TextUnit.wordsis logical — it matches raw counts for ordinary non-wide prose but differs for wide/fullwidth scripts, URLs, code, and symbolic content; useTextUnit.raw_wordsfor the previous whitespace-split behavior. Ripples throughsize(),size_summary(),section_size_tree(), and debug reports.- Token estimates scale logical words —
estimate_tokens(text, tokens_per_logical_word=1.6)replaces thechars_per_tokenparameter, andTOKENS_PER_LOGICAL_WORDreplacesCHARS_PER_TOKEN; estimates change numerically even at defaults. format_read_time()takes logical word counts — same signature; the default rate corresponds to roughly 450 CJK characters per minute under the default wide-character weight.- Pinned outputs change — regenerate any golden DocGraph/report snapshots (schema string,
sourceblock, andwordsfields).