调用方设计规范
Copy MarkdownZongzi 只负责纯数据与校验,不替 Caller 选型其状态形态(GenServer / LiveView assigns / 纯数据管道),因此即 Caller 管理的一组随编辑共同演进的状态——无法也不应由库实现。 以下把 Caller 必须自己维护的事项写成契约;若未来 Caller 形态收敛,可再长出共享 helper。
Caller 需要自持的组件
timeline—Zongzi.Timeline.t(),seq 全序真实源notes_by_seq—%{SeqID.t() => Note.t()},note 实体快照, 是 Windowing / Engine 的直接输入interventions—[Zongzi.Intervention.t()],AI 锚点集合, gc 与 rebase 的参照- tempo / beat 上下文 — tick ↔ ms 换算、播放头定位(Caller 业务自行决定)
同步契约
每次 Timeline 写操作后,Caller 必须按下表同步 notes_by_seq。
漏同步是最常见的踩坑点(见下节 Windowing 约束)。
insert_note/2、insert_note_before/3、insert_note_after/3→{:ok, tl, note}:Map.put(notes, note.seq_id, note)split_note/5→{:ok, tl, before, after}:before 保留原 seq、after 占新 seq, 两个都要写回merge_notes/4→{:ok, tl, merged}:seq_a 写回 merged,删 seq_b (seq_b 成为 merge 墓碑)delete_note/2→{:ok, tl}:删该 seqmove_note/4→{:ok, tl}:仅链序变化,notes_by_seq不动splice_after/3→{:ok, tl, notes}:逐个写回delete_range/3→{:ok, tl}:逐个删除gc/2→{:ok, tl}:物理移除无引用墓碑;notes_by_seq本就不该含墓碑, 无需动作- Note 层修改(
Note.drag_note/2、drag_duration/2、update_lyric/2、update_annotation/2、update_metadata/2)→{:ok, note}: 不经过 Timeline,Caller 自行按原 seq 写回
Windowing 约束
Zongzi.Windowing.Context.new/1 要求 notes_by_seq 覆盖全部 active seq,
缺任何一个都会返回 {:error, {:missing_notes_for_seq, seqs}}。
该错误几乎总是上表某一步漏同步所致。
编辑回路
一次编辑的完整回路:
{:ok, tl, note} = Timeline.insert_note(tl, note)
notes = Map.put(notes, note.seq_id, note)
%{ survived: ..., conflicts: ..., decisions: ...} = Anchor.rebase_all(ints, tl, ctx)
wctx = Windowing.Context.new(...)
{:ok, results} = Windowing.run_stages(wctx, stages)
# → Engine.check / Engine.render ...新 Note 的 ID 由 Caller 注入:Zongzi.Util.ID.generate_id("Note_")。