Changelog
Copy Markdown0.3.0 — 2026/07/23
0.3.0 focused on Timeline internals, Intervention validation, Engine render contract tightening, and extensive documentation/translation work.
Breaking Changes
Engine.render/1now acceptschecked_request(not barerequest):%{request: request(), artifact: check_artifact(), fingerprint: term()}. Implementations must verify the fingerprint matches current state before rendering. Return type extended with{:async, ref}for async pipelines. (4b97a2b)NoteTriplet.Options.orphan_directiondefault changed from:nextto:never: delete tombstones now return{:conflict, :adjacency_lost}without attempting relocation unless explicitly opted in.ScoredHost.Optionsfollows the same convention. (990c5c8)Interventionstruct: removed:scopefield: scope is now computed on-the-fly viaDeclaration.scope/2(pure function). Cached scope was stale after tempo/drag edits. (41184c7)- Delete tombstone simplified: removed SeqID comparison logic from
Timeline.delete; tombstone placement no longer depends on seq ordering. (ec35864)
Added
TimelineimplementsEnumerableprotocol — timelines are now directly iterable withEnum.*functions. (c1b42a7)Zongzi.Timeline.Link: extracted linked-list mechanics (insert, delete, neighborhood traversal) into a dedicated internal module. (2888801)Intervention.mount/5: new lifecycle function that validates anchor seq_ids against the timeline, rejecting interventions referencing inactive positions. (f2bb1ba)Engine.supports_render?/1: convenience guard for checking render callback availability. (4b97a2b)Intervention.validate/1: structural validation for strategy and declaration fields. (f2bb1ba)- MIT License added. (
bd30cd0)
Fixed
Timeline.gc/2now correctly removes orphan items fromseq_mapthat were previously leaked during undo operations. (8dbc735)Engine.render/1signature mismatch: callback accepted barerequestbut the dispatch contract expectedchecked_request. (4b97a2b)
Refactored
Timeline: reduced code lines; extracted linked-list mechanics toTimeline.Link; gc enhanced for undo scenarios. (e3dcc3b,2888801,9a69f69)Anchorrelocation logic refined for missing anchor edge cases;orphan_direction: :nevernow short-circuits before attempting relocate. (10069d5)Interventionresolution updated with proper typespecs forresolve_all/2. (82d6423,bf5a734)- Anchor option handling improved with cleaner defaults propagation.
(
092dee8)
Documentation
- Translated timeline, score, note module comments to English. (
0a00f7a,f752d8c,5666d47) - Added/updated glossary (
docs/zh/spec/GLOSSARY.md). (ce2d586,f752d8c,58c0195) - Added golden scenarios (
docs/zh/spec/GOLDEN_SCENARIOS.md) and mental models (docs/zh/spec/MENTAL_MODELS.md). (ce2d586) - Added Chinese guide: TheLittleZongzi-zh with Mermaid diagrams.
(
ea25ccf,d610efb,0b927b4) - Added CallerDesigning-zh design guide. (
055aa58) - Added decision records for intervention semantics, windowing post-rebase,
declaration projection, anchor orthogonality, and more
(
docs/zh/spec/decisions/). (ce2d586) - Migrated document snippets from moduledocs into guides. (
e11d645) - Refined architecture docs; added language links in README. (
3e897f8,5666d47)
0.2.0 — 2026/07/21
Contract revisions driven by Caller integration feedback (zongzi_feasibility), plus Strategy Options decoupling.
Breaking Changes
Declaration.on_rebase/3→on_rebase/4: added 4th argumentcontext(theAnchor.Contextinjected by the Caller intorebase_all, carryingnotes_by_seq). Declarations can now maintain payloads at tick granularity without the Caller pre-injecting split metadata. Strategy meta on relocate (from/to/method/scoring) is merged into the meta passed through, no longer discarded.Timeline.gc/2returns{:ok, t()}(was bare struct), consistent with the rest of the library's write operations. Also fixed a bug where gc was readingint.declaration.referenced_seqs/1— the correct source isint.strategy || NoteTriplet(declaration has no such callback in its contract).Strategy.rebase/3→rebase/4: added 4th argumentopts :: term()(a strategy-specific struct or map), unpacked from theIntervention.strategytuple and passed through by the dispatch layer. Custom strategies must adapt.Intervention.strategytype change:module() | nil→{module(), options :: term()} | nil. Whennil, dispatch falls back to{default_strategy, %{}}; each strategy normalizes the opts map into its own struct defaults.Anchor.Contextno longer carries strategy-level keys: removed:match_threshold,:allow_follow_merge,:orphan_direction, and:allow_relocate(the last two merged intoOptions.orphan_direction: :never). Shared keysnotes_by_seq,seq_to_window,focus_note,channel, andextraremain.allow_relocatemerged intoorphan_direction: :never:NoteTriplet.Options.orphan_directionaccepts:prev | :next | :never(default:next). When:never, a delete tombstone directly returns{:conflict, :adjacency_lost}without attempting relocation.ScoredHost.Optionsfollows the same convention.
Added
Anchor.rebase_all/4now returns a:decisionskey:%{intervention_id => :preserve | :rebase | :relocate | :split | :conflict}, allowing the Caller to consume structural decisions (metrics/logging) without re-deriving them from anchor diffs.Timeline.split_note/5: added optionalattrsparameter, forwarded toNote.split/4(e.g. to give the second half a different lyric).Zongzi.Anchor.NoteTriplet.Options: defstruct withmatch_threshold(default 2),allow_follow_merge(default false),orphan_direction(default:next).Zongzi.Anchor.ScoredHost.Options: same fields as NoteTriplet +scan_limit(default 4).- Both strategies include
normalize_opts/1, accepting%Options{}, a plain map, or anything else — always filling in struct defaults. The dispatch layer passes%{}for nil-strategy interventions; strategies tolerate this gracefully. - Bugfix:
do_relocatewas mistakenly receivingopts.allow_follow_merge(a boolean) instead ofopts.orphan_direction, which would cause aFunctionClauseErrorwhen the value hit thecasematching:prev | :next.
Refactored
Timeline.gc/2: dispatch layer unpacks the{module, opts}tuple before callingreferenced_seqs.Anchor.rebase_all/4: dispatch unpacks{strategy_mod, strategy_opts}and forwards opts.
Documentation
RestSplit3Beats: added caveat that intervention scope is a conservative upper bound that inflates windows; excess slack can fuse gaps that should have been separate windows.- Clarified that
on_rebase-split child interventions do not go throughstrategy.rebaseagain; anchor correctness is the declaration's responsibility.
0.1.0 — 2026/07/14
Initial release. Zongzi is a functional component library for the SVS domain, providing Score primitives, Timeline write paths, Anchor structural rebase, Intervention data contracts, and Engine/Windowing behaviour definitions.
Score Primitives
- Note / Tempo / TimeSig / Grid data structures and merge logic
- Key behaviour (with TwelveET implementation) and Note merge semantics
- Configurable TPQN (ticks per quarter note)
- RecordMap / TempoMap / TimeSigMap
Helpers.normalize_attrs/1
Timeline
- SeqID: permanent position identifier, generated from Timeline's own counter
note_order: ordered linked list + tombstones (merge / delete)- Write operations: insert / delete / split / merge / drag
Timeline.Queryread primitives:status/2,scan/4,neighborhood/3,scrub_triplet/2,hops/3gc/2: reclaim unreferenced tombstones
Anchor & Intervention
Interventionstruct: anchor, payload, snapshot, strategy, declaration, on_rebase callbackAnchor.Context: carries orphan_direction, strategy parameters, etc.Anchor.rebase_all/4: post-edit batch structural rebaseAnchor.NoteTripletstrategy: 3-of-3 exact → preserve; 2-of-3 → rebase; 0–1/3 → conflictAnchor.ScoredHost: scored relocation strategy for deleted/orphan notes (same key / same window)Intervention.Declarationbehaviour:scope/2,snapshot/2,resolve/2,on_rebase/3
Engine & Windowing
Enginebehaviour:check/1+ optionalrender/1, consumes only[Segment]Windowing.Strategybehaviour:window/1→[Segment]Windowing.WholeTrack: whole-track strategyWindowing.RestSplit3Beats: 3-beat split windowing strategyWindowing.Context/Windowing.Segment
Curve
- Adapter behaviour (replacing the original Protocol)
- Bezier / CatmullRom adapters
- Chunk / ControlPoint structs
Refactoring & Documentation
- Unified module naming: Slice → Segment, Host → Caller
- Timeline-related modules moved under Score namespace
- SeqID generation moved from Note into Timeline
Model.new/1requires explicit id (pure-function principle)- Complete mental-model documentation, design decision records, and golden-path
scenarios (
docs/zh/spec/)