4.8 KiB
Synced blocks: editable transclusion (agy gap 7, roadmap 36)
Reuse crates/blocks, engine block edit/buffers and current embed resolver. No new crate or dependency.
Research (2026-10-21; agy claims checked)
Notion confirms editing one synced instance changes other instances and edit access belongs to source: https://www.notion.com/help/synced-blocks . Adopt source ownership and propagation, not its cloud/copy storage format. Drop unverified product-list parity and agy's specific issue-number attribution. Existing NotesCraft embed rendering already resolves note/heading/block content, loads dirty buffers first and limits nesting; editable targets are the missing behavior, not transclusion itself.
Problem and goal
![[note#^id]] displays a source block but requires navigation to edit. An explicit Edit source block action edits that source from any embed while retaining one source markdown block and ordinary Obsidian syntax.
Design decisions
D1 Pure block target
Add crates/blocks/src/transclusion.rs, export from lib.rs. Resolve existing IDs using markdown block-id items and parse_blocks, not substring matching. Return {id,block_range,content_range,marker_range}; replace_by_id(source,id,expected_block,replacement) returns Edited. Support paragraphs, quote/callout/list blocks and standalone IDs following table/list/quote where supported by markdown contract. Preserve ID marker and line endings. Reject duplicate IDs, missing IDs, invalid UTF-8 offsets, oversized replacement and a replacement that injects/rebinds target IDs. Replacement is markdown content; preserve required structural markers or return unsupported. Keep unsupported targets readable with Open source action.
D2 Engine contract
block.transclusion.read {from,target} resolves wiki target through index relative to host; target must contain a #^id fragment; result {path,id,block,expected_block,editable,reason?}. block.transclusion.replace {path,id,expected_block,replacement} -> {path,id,changed}. Exact expected block is optimistic concurrency token (not a noncryptographic hash); resolve ID again against current buffer-first text at commit. Changes elsewhere in source may proceed, changed/deleted target returns conflict, duplicates return ambiguous_block. Serialize through session command dispatch; never read source then overwrite entire stale file. Use existing lint_commit/buffer edit flow so open target remains dirty, one undo step; closed target writes atomically through existing session path. Preserve source id and host embed bytes. Writes are assistant-gated. Validate vault paths and read-only policy before reading/editing.
D3 UI and propagation
Existing views/note_view/blocks/embed_data.rs preloads source; embed.rs paints it. New edit state keyed by (host path, resolved source path, block id, embed occurrence); no edits against rendered host offsets. Edit button shows source path; explicit Apply/Cancel, draft retained on conflict. Only one active draft per source target; another instance focuses it. After commit invalidate all embed caches for that path, including closed-file watcher changes, source tabs and split panes. No filesystem calls while painting. Self/cyclic embeds retain current depth 3 limit, additionally track (path,id) to show cycle placeholder. Cache never turns truncated source into editable target; target >1 MiB is read-only with explanation. Draft cap 1 MiB; 32 source notes and depth 3 remain bounded.
Acceptance criteria
- Two host notes embed one paragraph: editing either updates source and both renderings; host markdown byte-identical.
- Open source dirty buffer is authoritative; undo in source restores previous block, propagated everywhere.
- Stale target returns conflict without writes; unrelated source changes remain intact when ID resolves at a new offset.
- Missing/duplicate ID, readonly vault, malformed target, cycle and deleted source display diagnostics and never panic/write.
- CRLF, Unicode, nested list/quote, and standalone table ID round-trip with marker preserved; unsupported targets remain readable.
- Changing a closed source externally refreshes cached embeds; no repeated engine calls per row/frame.
Open owner questions
- Q1: approve explicit Apply/Cancel v1 versus keystroke-level autosave? Proposal explicit, matching conflict/undo semantics.
- Q2: approve source-buffer undo ownership? Proposal source owns undo; host offers Open source, no second undo log.
Feature ideas (researched, ranked)
- Show other embedding notes and navigate to source, inspired by Notion's editing-location list: https://www.notion.com/help/synced-blocks .
- Explicit Detach copy (host edit only), inspired by Notion unsync at the same URL; defer until safe source-range serialization exists.
- Multi-block targets require separate stable identity contract; do not copy Notion cloud identifiers into markdown.