Changelog
August 4, 2026 · View on GitHub
0.8.0
Unified trackability traversal and read-time id resolution for container blocks.
Added
tracked_blocks()andTrackedBlock— single traversal for node sync, extraction, and word counting.lifted_paragraph_id()(internal viaselection_id) — when ataskItem/listItem/blockquote/tableCellhas no container id but its direct childparagraphdoes, that id is used at read time without rewriting stored JSON.
Changed
text_slices()andword_count()now delegate totracked_blocks().blockquoteandtableCellblocks with ids are included in extraction and word counts (previously parser-visible but invisible to AI).has_open_tasks()only returnsTruewhen at least one task has a resolvable id and is not completed (fixes false positives on id-less task shells).syncable_tasks()usesselection_id()so lifted paragraph ids count.
0.7.0
text_slices() now treats listItem (bullet and ordered lists) as an
addressable text node, just like taskItem.
Changed
text/extract.pyaddsListItemto the content-node whitelist. Previously abulletList > listItemproduced 0 slices — the innerparagraphis suppressed bypolicy.is_parseable(single-identity rule) andlistItemitself was not collected, so the list item had no representative node and never reached topic extraction. Now eachlistItemyields one slice withnode_id= the listItem's id,text= its (possibly nested) inner text,html_tag = "li", andnode_type = "listItem".orderedList > listItemis covered by the same path automatically.
Top-level paragraph and taskList > taskItem behaviour is unchanged.
0.6.0
Pure raw-dict task namespace for working with TipTap taskList / taskItem
shapes without hydrating the typed AST. Complements the typed tasks package
(tasks = typed queries over Content; task = raw JSON dict helpers).
Added
task.create_list(items)wrapsitemsin a{"type": "taskList", "content": items}block. It stores the list by reference (nodeepcopy/copy) so callers can keep appending to the original list and see the change reflected in the block — a deliberate exception to the library's usual copy-on-construct behaviour.task.is_list(item)—Trueiffitemis a dict withtype == "taskList"and a listcontent. Safe on missing keys.task.is_item(node)—Trueiffnodeis a dict withtype == "taskItem". Safe on missing keys.
The task module is exported at the top level (from tiptap_python_utils import task) and imports only the contract layer (no model dependency).
0.5.0
Generic node construction and query helpers — a kind-agnostic convenience layer instead of per-kind shortcuts.
Added
codec.build_node(node_kind, text="", *, attrs=None, node_id=None)builds any typed node by hydrating a minimal raw payload through the registry, so subclass-typed fields (e.g.Heading.level) andpresentsemantics are derived exactly as when parsing JSON.node_idis only stamped whenattrscarries no identity of its own; container kinds reject inlinetext.Content.append(node_kind, text="", *, attrs=None, node_id=None)composesbuild_node+append_root, stamping a fresh id when none is given.Content.where(pred)andSelection.filter(pred)/Selection.any(pred)add generic predicate-based selection/query (replacing the need for kind-specific helpers like a heading-onlyhas_heading_text).new_node_id()(top-level +tiptap_python_utils.identity) generates a bare uuid id, distinct from theshared--prefixednew_shared_id().
0.4.0
Self-describing shared + place attrs in fingerprint/merge.
Changes
- Added
sharedandplaceattribute keys to the contract (contract/key.py). SharedFamilies.mergenow preserves the target's per-copyplacediscriminator while the family-identicalsharedcore rides along from the canonical body unchanged.fingerprintnow excludes theshared,place, and task canonical-id attrs so family identity ignores per-copy and self-describing fields.
0.3.0
Shared-node API aligned with the Content / Selection chain. The previous
dict-in / dict-out functional surface is gone — every shared-node operation is
now a method on Content, Node, or the new SharedFamilies value object.
Breaking changes
- Removed the functional shared API:
shared_families,sync_shared,has_shared,shared_id,stamp_shared,fingerprint_shared, andnormalize_shared_idno longer exist (neither at the package root nor undertiptap_python_utils.shared). - Migration:
shared_families(content)→Content.require(content).shared_families()(returns aSharedFamilies, not a raw dict).sync_shared(content, families)→Content.require(content).sync_shared(families)(returns a newContent; the(json, changed)tuple is gone — comparebefore.dump() != after.dump()if you need a change flag).has_shared(content, sid)→Content.require(content).has_shared(sid).shared_id(node)→node.shared_id(already aNodeproperty).stamp_shared(node, sid)→node.with_shared_id(sid). To override the local id as well, chain.with_attr("id", local_id).fingerprint_shared(raw_dict)→fingerprint(node)— now takes a typedNode, not a raw dict.
SharedFamiliesitself is immutable:__contains__,__getitem__,__iter__,__len__, and.merge(target)for per-node rewrites.
Added
Node.with_shared_id(value)mirrorswith_attrand returns a new node.Content.where_shared_id(sid) -> Selectionselects every parseable node with a matching sharedId.Content.has_shared(sid) -> bool,Content.shared_families() -> SharedFamilies, andContent.sync_shared(families) -> Contentreplace the old functional helpers.Selection.transform(fn)is a new public escape hatch for per-node rewrites:Selection(...).transform(lambda node: ...)returns a newContent. Used internally byContent.sync_shared.
Architecture
shared/sync.pydeleted; sync logic now lives onContentand reusesSelection.transform.shared/families.pyrewritten around the newSharedFamiliesdataclass, which holds canonicalNodebodies (not raw dicts) and exposes.merge.shared/fingerprint.pyoperates onNode, notdict.shared/identity.pycollapsed to justnew_shared_id().shared/__init__.pynow exportsSharedFamilies,fingerprint,new_shared_id(down from 8 names).- Duplicate
TaskItem.shared_idproperty removed — the baseNode.shared_idwas already covering it.
0.2.0
Architecture refactor (audit phases 1–4). Public surface is reorganized; behavior is unchanged for everything that was retained.
Breaking changes
- Removed the
tiptap_python_utils.editpackage. The two document-level commands it exported are now methods onContent:append_node(doc, node)→Content.require(doc).append_root(node).dump()replace_node(doc, id, node)→Content.require(doc).replace_by_id(id, node).dump()
Selection.text(...)andSelection.marks(...)are now strict and require a text ref. Chain.leaf()first to descend from a container, e.g.where_id("p1").leaf().text("New").Heading.levelis now a@propertyderived fromattrs.level. TheHeading(level=...)keyword argument was removed; set the value viaattrs={"level": N}orselection.attr("level", N).TaskItemstored fields (task_item_id,is_completed,local_task_item_id,canonical_task_item_id,is_linked_copy) are now@propertyderived fromattrs/extra. Construct task items viaTaskItem(attrs={"id": ..., "checked": ...}, ...); the old keyword arguments were removed.
Architecture
model/__init__.pyis now a re-export barrel. Implementation moved tomodel/base.py,model/nodes.py,model/registry.py,model/payload.py. All previous import paths still work.codec/json.pywas split intocodec/raw.py(JSON I/O + dict-shape helpers, zero..modeldependency),codec/reader.py(raw → typed AST), andcodec/writer.py(typed → raw + dump). Barrel exports unchanged.shared/service.pywas split intoshared/identity.py,shared/fingerprint.py,shared/families.py, andshared/sync.py. Barrel exports unchanged.Contentgainedappend_root(node_or_raw)andreplace_by_id(id, node_or_raw)document-level methods.Selectiongained.leaf()to descend to the first text descendant of each ref.
Added
tests/test_codec_raw.pyexercises the raw codec layer in isolation, including an invariant test that ensurescodec.rawnever imports..model.tests/test_compat_imports.pyandtests/test_public_api.pysnapshot the importable surface so future refactors can move implementation without breaking imports.- README documents
Content.parse/require/wrap, the lossless round-trip mechanism, document-level commands,TaskItemderived properties, and an end-to-endshared_families/sync_sharedexample.
0.1.0
- Initial public package structure.
- Typed TipTap AST model and content facade.
- JSON parsing and serialization with lossless unknown-node round trips.
- Traversal, selection, immutable edit helpers, text extraction, task helpers, and shared-node synchronization.